Merge branch 'docmirror:master' into only-edit-wiki
commit
38638e16d2
|
|
@ -393,6 +393,7 @@ const defaultConfig = {
|
||||||
type: 'https',
|
type: 'https',
|
||||||
server: 'https://doh.360.cn/dns-query',
|
server: 'https://doh.360.cn/dns-query',
|
||||||
cacheSize: 1000,
|
cacheSize: 1000,
|
||||||
|
forSNI: true,
|
||||||
},
|
},
|
||||||
rubyfish: {
|
rubyfish: {
|
||||||
type: 'https',
|
type: 'https',
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ function parseVersion (version) {
|
||||||
* @param log 日志对象
|
* @param log 日志对象
|
||||||
* @returns {number} 比较线上版本号是否为更新的版本,大于0=是|0=相等|小于0=否|-999=出现异常,比较结果未知
|
* @returns {number} 比较线上版本号是否为更新的版本,大于0=是|0=相等|小于0=否|-999=出现异常,比较结果未知
|
||||||
*/
|
*/
|
||||||
export function isNewVersion (onlineVersion, currentVersion, log = console) {
|
export function isNewVersion (onlineVersion, currentVersion, log = null) {
|
||||||
if (onlineVersion === currentVersion) {
|
if (onlineVersion === currentVersion) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -410,7 +410,7 @@ function registerShowHideShortcut (showHideShortcut) {
|
||||||
function initApp () {
|
function initApp () {
|
||||||
if (isMac) {
|
if (isMac) {
|
||||||
app.whenReady().then(() => {
|
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) {
|
function install (app, api) {
|
||||||
api.ipc.on('error.core', (event, message) => {
|
api.ipc.on('error.core', (event, message) => {
|
||||||
console.error('view on error', message)
|
console.error('view on error', message)
|
||||||
|
|
@ -13,11 +15,17 @@ function install (app, api) {
|
||||||
|
|
||||||
function handleServerStartError (message, err, app, api) {
|
function handleServerStartError (message, err, app, api) {
|
||||||
if (message.value === 'EADDRINUSE') {
|
if (message.value === 'EADDRINUSE') {
|
||||||
|
// 避免重复弹窗
|
||||||
|
const now = Date.now()
|
||||||
|
if (latestConfirmTime != null && now - latestConfirmTime < 1000) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
latestConfirmTime = now
|
||||||
|
|
||||||
app.$confirm({
|
app.$confirm({
|
||||||
title: '端口被占用,代理服务启动失败',
|
title: '端口被占用,代理服务启动失败',
|
||||||
content: '是否要杀掉占用进程?您也可以点击取消,然后前往加速服务->基本设置中修改代理端口',
|
content: '是否要杀掉占用进程?您也可以点击取消,然后前往加速服务->基本设置中修改代理端口',
|
||||||
onOk () {
|
onOk () {
|
||||||
// TODO 杀掉进程
|
|
||||||
api.config.get().then((config) => {
|
api.config.get().then((config) => {
|
||||||
console.log('config:', config)
|
console.log('config:', config)
|
||||||
api.shell.killByPort({ port: config.server.port }).then((ret) => {
|
api.shell.killByPort({ port: config.server.port }).then((ret) => {
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,19 @@ export default {
|
||||||
const dir = await this.$api.info.getLogDir()
|
const dir = await this.$api.info.getLogDir()
|
||||||
this.$api.ipc.openPath(dir)
|
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) {
|
handleHostname (hostname) {
|
||||||
if (this.isNotHostname(hostname)) {
|
if (this.isNotHostname(hostname)) {
|
||||||
return ''
|
return ''
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ export default {
|
||||||
},
|
},
|
||||||
addNoProxyUrl () {
|
addNoProxyUrl () {
|
||||||
this.noProxyUrls.unshift({ key: '' })
|
this.noProxyUrls.unshift({ key: '' })
|
||||||
|
this.focusFirst(this.$refs.noProxyUrls)
|
||||||
},
|
},
|
||||||
delNoProxyUrl (item, index) {
|
delNoProxyUrl (item, index) {
|
||||||
this.noProxyUrls.splice(index, 1)
|
this.noProxyUrls.splice(index, 1)
|
||||||
|
|
@ -108,7 +109,7 @@ export default {
|
||||||
<a-button type="primary" icon="plus" @click="addNoProxyUrl()" />
|
<a-button type="primary" icon="plus" @click="addNoProxyUrl()" />
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</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">
|
<a-col :span="22">
|
||||||
<MockInput v-model="item.key" class="mt-2" />
|
<MockInput v-model="item.key" class="mt-2" />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ export default {
|
||||||
},
|
},
|
||||||
addTarget () {
|
addTarget () {
|
||||||
this.targets.unshift({ key: '', value: 'true' })
|
this.targets.unshift({ key: '', value: 'true' })
|
||||||
|
this.focusFirst(this.$refs.targets)
|
||||||
},
|
},
|
||||||
deleteTarget (item, index) {
|
deleteTarget (item, index) {
|
||||||
this.targets.splice(index, 1)
|
this.targets.splice(index, 1)
|
||||||
|
|
@ -88,14 +89,17 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (this.servers.length === 0) {
|
if (this.servers.length === 0) {
|
||||||
this.addServer()
|
this.addServer(false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteServer (item, index) {
|
deleteServer (item, index) {
|
||||||
this.servers.splice(index, 1)
|
this.servers.splice(index, 1)
|
||||||
},
|
},
|
||||||
addServer () {
|
addServer (needFocus = true) {
|
||||||
this.servers.unshift({ key: '', value: { type: 'path' } })
|
this.servers.unshift({ key: '', value: { type: 'path' } })
|
||||||
|
if (needFocus) {
|
||||||
|
this.focusFirst(this.$refs.servers)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
submitServer () {
|
submitServer () {
|
||||||
const map = {}
|
const map = {}
|
||||||
|
|
@ -169,7 +173,7 @@ export default {
|
||||||
<a-button type="primary" icon="plus" @click="addTarget()" />
|
<a-button type="primary" icon="plus" @click="addTarget()" />
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</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">
|
<a-col :span="18">
|
||||||
<MockInput v-model="item.key" class="mt-2" />
|
<MockInput v-model="item.key" class="mt-2" />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
@ -196,7 +200,7 @@ export default {
|
||||||
<a-button type="primary" icon="plus" @click="addServer()" />
|
<a-button type="primary" icon="plus" @click="addServer()" />
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</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-col :span="6">
|
||||||
<a-input v-model="item.key" :title="item.key" addon-before="域名" placeholder="yourdomain.com" spellcheck="false" />
|
<a-input v-model="item.key" :title="item.key" addon-before="域名" placeholder="yourdomain.com" spellcheck="false" />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ export default {
|
||||||
},
|
},
|
||||||
addExcludeIp () {
|
addExcludeIp () {
|
||||||
this.excludeIpList.unshift({ key: '', value: 'true' })
|
this.excludeIpList.unshift({ key: '', value: 'true' })
|
||||||
|
this.focusFirst(this.$refs.excludeIpList)
|
||||||
},
|
},
|
||||||
delExcludeIp (item, index) {
|
delExcludeIp (item, index) {
|
||||||
this.excludeIpList.splice(index, 1)
|
this.excludeIpList.splice(index, 1)
|
||||||
|
|
@ -166,7 +167,7 @@ export default {
|
||||||
<a-button type="primary" icon="plus" @click="addExcludeIp()" />
|
<a-button type="primary" icon="plus" @click="addExcludeIp()" />
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</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">
|
<a-col :span="17">
|
||||||
<MockInput v-model="item.key" class="mt-1" />
|
<MockInput v-model="item.key" class="mt-1" />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ export default {
|
||||||
},
|
},
|
||||||
addDnsMapping () {
|
addDnsMapping () {
|
||||||
this.dnsMappings.unshift({ key: '', value: 'quad9' })
|
this.dnsMappings.unshift({ key: '', value: 'quad9' })
|
||||||
|
this.focusFirst(this.$refs.dnsMappings)
|
||||||
},
|
},
|
||||||
|
|
||||||
// whiteList
|
// whiteList
|
||||||
|
|
@ -123,6 +124,7 @@ export default {
|
||||||
},
|
},
|
||||||
addWhiteList () {
|
addWhiteList () {
|
||||||
this.whiteList.unshift({ key: '', value: 'true' })
|
this.whiteList.unshift({ key: '', value: 'true' })
|
||||||
|
this.focusFirst(this.$refs.whiteList)
|
||||||
},
|
},
|
||||||
deleteWhiteList (item, index) {
|
deleteWhiteList (item, index) {
|
||||||
this.whiteList.splice(index, 1)
|
this.whiteList.splice(index, 1)
|
||||||
|
|
@ -144,6 +146,7 @@ export default {
|
||||||
},
|
},
|
||||||
addSpeedHostname () {
|
addSpeedHostname () {
|
||||||
this.getSpeedTestConfig().hostnameList.unshift('')
|
this.getSpeedTestConfig().hostnameList.unshift('')
|
||||||
|
this.focusFirst(this.$refs.hostnameList)
|
||||||
},
|
},
|
||||||
delSpeedHostname (item, index) {
|
delSpeedHostname (item, index) {
|
||||||
this.getSpeedTestConfig().hostnameList.splice(index, 1)
|
this.getSpeedTestConfig().hostnameList.splice(index, 1)
|
||||||
|
|
@ -310,13 +313,13 @@ export default {
|
||||||
<div v-if="activeTabKey === '4'">
|
<div v-if="activeTabKey === '4'">
|
||||||
<a-row style="margin-top:10px">
|
<a-row style="margin-top:10px">
|
||||||
<a-col span="21">
|
<a-col span="21">
|
||||||
<div>这里配置的域名不会通过代理</div>
|
<div>配置为<code>不代理</code>的域名不会通过代理</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col span="3">
|
<a-col span="3">
|
||||||
<a-button style="margin-left:8px" type="primary" icon="plus" @click="addWhiteList()" />
|
<a-button style="margin-left:8px" type="primary" icon="plus" @click="addWhiteList()" />
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</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">
|
<a-col :span="16">
|
||||||
<MockInput v-model="item.key" />
|
<MockInput v-model="item.key" />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
@ -374,7 +377,7 @@ export default {
|
||||||
<a-button style="margin-left:8px" type="primary" icon="plus" @click="addDnsMapping()" />
|
<a-button style="margin-left:8px" type="primary" icon="plus" @click="addDnsMapping()" />
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</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">
|
<a-col :span="15">
|
||||||
<MockInput v-model="item.key" />
|
<MockInput v-model="item.key" />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
@ -422,7 +425,7 @@ export default {
|
||||||
<a-button style="margin-left:10px" type="primary" icon="plus" @click="addSpeedHostname()" />
|
<a-button style="margin-left:10px" type="primary" icon="plus" @click="addSpeedHostname()" />
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</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">
|
<a-col :span="21">
|
||||||
<MockInput v-model="getSpeedTestConfig().hostnameList[index]" />
|
<MockInput v-model="getSpeedTestConfig().hostnameList[index]" />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
@ -452,9 +455,9 @@ export default {
|
||||||
</a>
|
</a>
|
||||||
<a-tag
|
<a-tag
|
||||||
v-for="(element, index) of item.backupList" :key="index" style="margin:2px;"
|
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-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,12 @@ $dark-input: #777; //输入框:背景色
|
||||||
border-color: #5a5750;
|
border-color: #5a5750;
|
||||||
color: #cfa572;
|
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) {
|
.ant-btn:not(.ant-btn-danger, .ant-btn-primary) {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"baidu-aip-sdk": "^4.16.16",
|
"baidu-aip-sdk": "^4.16.16",
|
||||||
"dns-over-http": "^0.2.0",
|
"dns-over-http": "^0.2.0",
|
||||||
"dns-over-tls": "^0.0.9",
|
|
||||||
"is-browser": "^2.1.0",
|
"is-browser": "^2.1.0",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|
|
||||||
|
|
@ -52,21 +52,26 @@ module.exports = class BaseDNS {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async lookup (hostname) {
|
async lookup (hostname, ipChecker) {
|
||||||
try {
|
try {
|
||||||
let ipCache = this.cache.get(hostname)
|
let ipCache = this.cache.get(hostname)
|
||||||
if (ipCache) {
|
if (ipCache) {
|
||||||
if (ipCache.value != null) {
|
const ip = ipCache.value
|
||||||
ipCache.doCount(ipCache.value, false)
|
if (ip != null) {
|
||||||
return ipCache.value
|
if (ipChecker && ipChecker(ip)) {
|
||||||
|
ipCache.doCount(ip, false)
|
||||||
|
return ip
|
||||||
|
} else {
|
||||||
|
return hostname
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ipCache = new IpCache(hostname)
|
ipCache = new IpCache(hostname)
|
||||||
this.cache.set(hostname, ipCache)
|
this.cache.set(hostname, ipCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
const t = new Date()
|
const t = Date.now()
|
||||||
let ipList = await this._lookupInternal(hostname)
|
let ipList = await this._lookupWithPreSetIpList(hostname)
|
||||||
if (ipList == null) {
|
if (ipList == null) {
|
||||||
// 没有获取到ipv4地址
|
// 没有获取到ipv4地址
|
||||||
ipList = []
|
ipList = []
|
||||||
|
|
@ -74,55 +79,126 @@ module.exports = class BaseDNS {
|
||||||
ipList.push(hostname) // 把原域名加入到统计里去
|
ipList.push(hostname) // 把原域名加入到统计里去
|
||||||
|
|
||||||
ipCache.setBackupList(ipList)
|
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) {
|
} catch (error) {
|
||||||
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] cannot resolve hostname ${hostname}, error:`, error)
|
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] cannot resolve hostname ${hostname}, error:`, error)
|
||||||
return hostname
|
return hostname
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _lookupInternal (hostname) {
|
async _lookupWithPreSetIpList (hostname) {
|
||||||
|
if (this.preSetIpList) {
|
||||||
// 获取当前域名的预设IP列表
|
// 获取当前域名的预设IP列表
|
||||||
let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, `matched preSetIpList(${this.dnsName})`)
|
let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, `matched preSetIpList(${this.dnsName})`)
|
||||||
if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) {
|
if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) {
|
||||||
if (hostnamePreSetIpList.length > 0) {
|
if (hostnamePreSetIpList.length > 0) {
|
||||||
hostnamePreSetIpList = hostnamePreSetIpList.slice()
|
hostnamePreSetIpList = hostnamePreSetIpList.slice() // 复制一份列表数据,避免配置数据被覆盖
|
||||||
} else {
|
} else {
|
||||||
hostnamePreSetIpList = mapToList(hostnamePreSetIpList)
|
hostnamePreSetIpList = mapToList(hostnamePreSetIpList)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hostnamePreSetIpList.length > 0) {
|
if (hostnamePreSetIpList.length > 0) {
|
||||||
hostnamePreSetIpList.isPreSet = true
|
hostnamePreSetIpList.isPreSet = true
|
||||||
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的预设IP列表: ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`)
|
log.info(`[DNS-over-PreSet '${this.dnsName}'] 获取到该域名的预设IP列表: ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`)
|
||||||
return hostnamePreSetIpList
|
return hostnamePreSetIpList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return await this._lookup(hostname)
|
return await this._lookup(hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
async _lookup (hostname) {
|
async _lookup (hostname) {
|
||||||
const start = Date.now()
|
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 {
|
try {
|
||||||
const response = await this._doDnsQuery(hostname)
|
|
||||||
const cost = Date.now() - start
|
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) {
|
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)
|
log.warn(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IP地址: ${hostname}, cost: ${cost} ms, response:`, response)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const ret = response.answers.filter(item => item.type === 'A').map(item => item.data)
|
const ret = response.answers.filter(item => item.type === 'A').map(item => item.data)
|
||||||
if (ret.length === 0) {
|
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 {
|
} 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
|
return ret
|
||||||
} catch (e) {
|
} 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 []
|
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 { promisify } = require('node:util')
|
||||||
const doh = require('dns-over-http')
|
const doh = require('dns-over-http')
|
||||||
const BaseDNS = require('./base')
|
const BaseDNS = require('./base')
|
||||||
|
const HttpsAgent = require('../proxy/common/ProxyHttpsAgent')
|
||||||
|
const Agent = require('../proxy/common/ProxyHttpAgent')
|
||||||
|
|
||||||
const dohQueryAsync = promisify(doh.query)
|
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 {
|
module.exports = class DNSOverHTTPS extends BaseDNS {
|
||||||
constructor (dnsName, cacheSize, preSetIpList, dnsServer) {
|
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerName) {
|
||||||
super(dnsName, 'HTTPS', cacheSize, preSetIpList)
|
super(dnsName, 'HTTPS', cacheSize, preSetIpList)
|
||||||
this.dnsServer = dnsServer
|
this.dnsServer = dnsServer.replace(/\s+/, '')
|
||||||
|
this.dnsServerName = dnsServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
async _doDnsQuery (hostname) {
|
_dnsQueryPromise (hostname, type = 'A') {
|
||||||
return await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }])
|
// 请求参数
|
||||||
|
const options = {
|
||||||
|
url: this.dnsServer,
|
||||||
|
agent: createAgent(this.dnsServer),
|
||||||
|
}
|
||||||
|
if (this.dnsServerName) {
|
||||||
|
// 设置SNI
|
||||||
|
options.servername = this.dnsServerName
|
||||||
|
options.rejectUnauthorized = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNS查询参数
|
||||||
|
const questions = [
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
name: hostname,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return dohQueryAsync(options, questions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
const matchUtil = require('../../utils/util.match')
|
const matchUtil = require('../../utils/util.match')
|
||||||
|
const log = require('../../utils/util.log.server')
|
||||||
const DNSOverPreSetIpList = require('./preset.js')
|
const DNSOverPreSetIpList = require('./preset.js')
|
||||||
const DNSOverHTTPS = require('./https.js')
|
const DNSOverHTTPS = require('./https.js')
|
||||||
const DNSOverTLS = require('./tls.js')
|
const DNSOverTLS = require('./tls.js')
|
||||||
|
|
@ -27,7 +28,7 @@ module.exports = {
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
if (server.startsWith('https://') || server.startsWith('http://')) {
|
if (server.startsWith('https://') || server.startsWith('http://')) {
|
||||||
type = 'https'
|
type = 'https'
|
||||||
} else if (server.startsWith('tls://')) {
|
} else if (server.startsWith('tls://') || server.startsWith('dot://')) {
|
||||||
type = 'tls'
|
type = 'tls'
|
||||||
} else if (server.startsWith('tcp://')) {
|
} else if (server.startsWith('tcp://')) {
|
||||||
type = 'tcp'
|
type = 'tcp'
|
||||||
|
|
@ -47,7 +48,7 @@ module.exports = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 基于 https
|
// 基于 https
|
||||||
dnsMap[provider] = new DNSOverHTTPS(provider, conf.cacheSize, preSetIpList, server)
|
dnsMap[provider] = new DNSOverHTTPS(provider, conf.cacheSize, preSetIpList, server, conf.sni || conf.servername)
|
||||||
} else {
|
} else {
|
||||||
// 获取DNS端口
|
// 获取DNS端口
|
||||||
let port = conf.port
|
let port = conf.port
|
||||||
|
|
@ -63,8 +64,8 @@ module.exports = {
|
||||||
|
|
||||||
if (type === 'tls' || type === 'dot' || type === 'dns-over-tls') {
|
if (type === 'tls' || type === 'dot' || type === 'dns-over-tls') {
|
||||||
// 基于 tls
|
// 基于 tls
|
||||||
dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.servername)
|
dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.sni || conf.servername)
|
||||||
} else if (type === 'tcp' || type === 'dns-over-tcp') {
|
} else if (type === 'tcp') {
|
||||||
// 基于 tcp
|
// 基于 tcp
|
||||||
dnsMap[provider] = new DNSOverTCP(provider, conf.cacheSize, preSetIpList, server, port)
|
dnsMap[provider] = new DNSOverTCP(provider, conf.cacheSize, preSetIpList, server, port)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -72,10 +73,19 @@ module.exports = {
|
||||||
dnsMap[provider] = new DNSOverUDP(provider, conf.cacheSize, preSetIpList, server, port)
|
dnsMap[provider] = new DNSOverUDP(provider, conf.cacheSize, preSetIpList, server, port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (conf.forSNI || conf.forSni) {
|
||||||
|
dnsMap.ForSNI = dnsMap[provider]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建预设IP的DNS
|
// 创建预设IP的DNS
|
||||||
dnsMap.PreSet = new DNSOverPreSetIpList(preSetIpList)
|
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
|
return dnsMap
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,16 @@ const dnsPacket = require('dns-packet')
|
||||||
const randi = require('random-int')
|
const randi = require('random-int')
|
||||||
const BaseDNS = require('./base')
|
const BaseDNS = require('./base')
|
||||||
|
|
||||||
const defaultPort = 53 // UDP类型的DNS服务默认端口号
|
const defaultPort = 53 // TCP类型的DNS服务默认端口号
|
||||||
|
|
||||||
module.exports = class DNSOverTCP extends BaseDNS {
|
module.exports = class DNSOverTCP extends BaseDNS {
|
||||||
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) {
|
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) {
|
||||||
super(dnsName, 'TCP', cacheSize, preSetIpList)
|
super(dnsName, 'TCP', cacheSize, preSetIpList)
|
||||||
this.dnsServer = dnsServer
|
this.dnsServer = dnsServer.replace(/\s+/, '')
|
||||||
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
|
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
|
||||||
}
|
}
|
||||||
|
|
||||||
_doDnsQuery (hostname) {
|
_dnsQueryPromise (hostname, type = 'A') {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// 构造 DNS 查询报文
|
// 构造 DNS 查询报文
|
||||||
const packet = dnsPacket.encode({
|
const packet = dnsPacket.encode({
|
||||||
|
|
@ -21,7 +21,7 @@ module.exports = class DNSOverTCP extends BaseDNS {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
id: randi(0x0, 0xFFFF),
|
id: randi(0x0, 0xFFFF),
|
||||||
questions: [{
|
questions: [{
|
||||||
type: 'A',
|
type,
|
||||||
name: hostname,
|
name: hostname,
|
||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const dnstls = require('dns-over-tls')
|
const dnstls = require('./util/dns-over-tls')
|
||||||
const BaseDNS = require('./base')
|
const BaseDNS = require('./base')
|
||||||
|
|
||||||
const defaultPort = 853
|
const defaultPort = 853
|
||||||
|
|
@ -6,22 +6,25 @@ const defaultPort = 853
|
||||||
module.exports = class DNSOverTLS extends BaseDNS {
|
module.exports = class DNSOverTLS extends BaseDNS {
|
||||||
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort, dnsServerName) {
|
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort, dnsServerName) {
|
||||||
super(dnsName, 'TLS', cacheSize, preSetIpList)
|
super(dnsName, 'TLS', cacheSize, preSetIpList)
|
||||||
this.dnsServer = dnsServer
|
this.dnsServer = dnsServer.replace(/\s+/, '')
|
||||||
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
|
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
|
||||||
this.dnsServerName = dnsServerName
|
this.dnsServerName = dnsServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
async _doDnsQuery (hostname) {
|
_dnsQueryPromise (hostname, type = 'A') {
|
||||||
const options = {
|
const options = {
|
||||||
host: this.dnsServer,
|
host: this.dnsServer,
|
||||||
port: this.dnsServerPort,
|
port: this.dnsServerPort,
|
||||||
servername: this.dnsServerName || this.dnsServer,
|
servername: this.dnsServerName || this.dnsServer,
|
||||||
|
rejectUnauthorized: !this.dnsServerName,
|
||||||
|
|
||||||
name: hostname,
|
name: hostname,
|
||||||
klass: 'IN',
|
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 {
|
module.exports = class DNSOverUDP extends BaseDNS {
|
||||||
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) {
|
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) {
|
||||||
super(dnsName, 'UDP', cacheSize, preSetIpList)
|
super(dnsName, 'UDP', cacheSize, preSetIpList)
|
||||||
this.dnsServer = dnsServer
|
this.dnsServer = dnsServer.replace(/\s+/, '')
|
||||||
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
|
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
|
||||||
|
|
||||||
this.isIPv6 = dnsServer.includes(':') && dnsServer.includes('[') && dnsServer.includes(']')
|
this.isIPv6 = dnsServer.includes(':') && dnsServer.includes('[') && dnsServer.includes(']')
|
||||||
this.socketType = this.isIPv6 ? 'udp6' : 'udp4'
|
this.socketType = this.isIPv6 ? 'udp6' : 'udp4'
|
||||||
}
|
}
|
||||||
|
|
||||||
_doDnsQuery (hostname) {
|
_dnsQueryPromise (hostname, type = 'A') {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
let isOver = false
|
||||||
|
const timeout = 5000
|
||||||
|
let timeoutId = null
|
||||||
|
|
||||||
// 构造 DNS 查询报文
|
// 构造 DNS 查询报文
|
||||||
const packet = dnsPacket.encode({
|
const packet = dnsPacket.encode({
|
||||||
flags: dnsPacket.RECURSION_DESIRED,
|
flags: dnsPacket.RECURSION_DESIRED,
|
||||||
type: 'query',
|
type: 'query',
|
||||||
id: randi(0x0, 0xFFFF),
|
id: randi(0x0, 0xFFFF),
|
||||||
questions: [{
|
questions: [{
|
||||||
type: 'A',
|
type,
|
||||||
name: hostname,
|
name: hostname,
|
||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
|
|
||||||
// 创建客户端
|
// 创建客户端
|
||||||
const udpClient = dgram.createSocket(this.socketType, (msg, _rinfo) => {
|
const udpClient = dgram.createSocket(this.socketType, (msg, _rinfo) => {
|
||||||
|
isOver = true
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
|
||||||
const response = dnsPacket.decode(msg)
|
const response = dnsPacket.decode(msg)
|
||||||
resolve(response)
|
resolve(response)
|
||||||
udpClient.close()
|
udpClient.close()
|
||||||
|
|
@ -38,10 +45,20 @@ module.exports = class DNSOverUDP extends BaseDNS {
|
||||||
// 发送 UDP 查询
|
// 发送 UDP 查询
|
||||||
udpClient.send(packet, 0, packet.length, this.dnsServerPort, this.dnsServer, (err, _bytes) => {
|
udpClient.send(packet, 0, packet.length, this.dnsServerPort, this.dnsServer, (err, _bytes) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
isOver = true
|
||||||
|
clearTimeout(timeoutId)
|
||||||
reject(err)
|
reject(err)
|
||||||
udpClient.close()
|
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
|
const { rOptions, log } = context
|
||||||
|
|
||||||
if (interceptOpt.abort === true || interceptOpt.abort === 'true') {
|
if (interceptOpt.abort === true || interceptOpt.abort === 'true') {
|
||||||
res.writeHead(403, {
|
const headers = {
|
||||||
'Content-Type': 'text/plain; charset=utf-8',
|
'Content-Type': 'text/plain; charset=utf-8',
|
||||||
'DS-Interceptor': 'abort',
|
'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(
|
res.write(
|
||||||
'DevSidecar 403: Request abort.\n\n'
|
'DevSidecar 403: Request abort.\n\n'
|
||||||
+ ' This request is matched by abort intercept.\n\n'
|
+ ' This request is matched by abort intercept.\n\n'
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ function checkIsLimitConfig (id, api) {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: 'baiduOcr',
|
name: 'baiduOcr',
|
||||||
priority: 131,
|
priority: 131,
|
||||||
requestIntercept (context, interceptOpt, req, res, ssl, next, matched) {
|
requestIntercept (context, interceptOpt, req, res, ssl, next) {
|
||||||
const { rOptions, log } = context
|
const { rOptions, log } = context
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,29 @@
|
||||||
const url = require('node:url')
|
const url = require('node:url')
|
||||||
const lodash = require('lodash')
|
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('${')) {
|
if (url.includes('${')) {
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
url = url.replace('${host}', rOptions.hostname)
|
url = url.replace('${host}', rOptions.hostname)
|
||||||
|
|
||||||
if (matched && url.includes('${')) {
|
if (url.includes('${')) {
|
||||||
for (let i = 0; i < matched.length; i++) {
|
url = replacePlaceholder0(url, pathMatched, 'p')
|
||||||
url = url.replace(`\${m[${i}]}`, matched[i] == null ? '' : matched[i])
|
url = replacePlaceholder0(url, hostnameMatched, 'h')
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除多余的占位符
|
// 移除多余的占位符
|
||||||
|
|
@ -22,7 +35,7 @@ function replacePlaceholder (url, rOptions, matched) {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTargetUrl (rOptions, urlConf, interceptOpt, matched) {
|
function buildTargetUrl (rOptions, urlConf, interceptOpt, matched, hostnameMatched) {
|
||||||
let targetUrl
|
let targetUrl
|
||||||
if (interceptOpt && interceptOpt.replace) {
|
if (interceptOpt && interceptOpt.replace) {
|
||||||
const regexp = new RegExp(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}`
|
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
|
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的属性
|
// 替换rOptions的属性
|
||||||
// eslint-disable-next-line node/no-deprecated-api
|
// eslint-disable-next-line node/no-deprecated-api
|
||||||
|
|
@ -76,7 +89,7 @@ module.exports = {
|
||||||
replacePlaceholder,
|
replacePlaceholder,
|
||||||
buildTargetUrl,
|
buildTargetUrl,
|
||||||
doProxy,
|
doProxy,
|
||||||
requestIntercept (context, interceptOpt, req, res, ssl, next, matched) {
|
requestIntercept (context, interceptOpt, req, res, ssl, next, matched, hostnameMatched) {
|
||||||
const { rOptions, log, RequestCounter } = context
|
const { rOptions, log, RequestCounter } = context
|
||||||
|
|
||||||
const originHostname = rOptions.hostname
|
const originHostname = rOptions.hostname
|
||||||
|
|
@ -107,7 +120,7 @@ module.exports = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 替换 rOptions 中的地址,并返回代理目标地址
|
// 替换 rOptions 中的地址,并返回代理目标地址
|
||||||
const proxyTarget = doProxy(proxyConf, rOptions, req, interceptOpt, matched)
|
const proxyTarget = doProxy(proxyConf, rOptions, req, interceptOpt, matched, hostnameMatched)
|
||||||
|
|
||||||
if (context.requestCount) {
|
if (context.requestCount) {
|
||||||
log.info('proxy choice:', JSON.stringify(context.requestCount))
|
log.info('proxy choice:', JSON.stringify(context.requestCount))
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,24 @@ const proxyApi = require('./proxy')
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: 'redirect',
|
name: 'redirect',
|
||||||
priority: 105,
|
priority: 105,
|
||||||
requestIntercept (context, interceptOpt, req, res, ssl, next, matched) {
|
requestIntercept (context, interceptOpt, req, res, ssl, next, matched, hostnameMatched) {
|
||||||
const { rOptions, log } = context
|
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,
|
'Location': redirect,
|
||||||
'DS-Interceptor': '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()
|
res.end()
|
||||||
|
|
||||||
const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
|
const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,18 @@ module.exports = {
|
||||||
const { rOptions, log } = context
|
const { rOptions, log } = context
|
||||||
|
|
||||||
if (interceptOpt.success === true || interceptOpt.success === 'true') {
|
if (interceptOpt.success === true || interceptOpt.success === 'true') {
|
||||||
res.writeHead(200, {
|
const headers = {
|
||||||
'Content-Type': 'text/plain; charset=utf-8',
|
'Content-Type': 'text/plain; charset=utf-8',
|
||||||
'DS-Interceptor': 'success',
|
'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(
|
res.write(
|
||||||
'DevSidecar 200: Request success.\n\n'
|
'DevSidecar 200: Request success.\n\n'
|
||||||
+ ' This request is matched by success intercept.\n\n'
|
+ ' This request is matched by success intercept.\n\n'
|
||||||
|
|
|
||||||
|
|
@ -92,9 +92,9 @@ module.exports = {
|
||||||
// 如果未手动配置需要缓存,则不允许使用缓存
|
// 如果未手动配置需要缓存,则不允许使用缓存
|
||||||
const maxAge = cacheReq.getMaxAge(interceptOpt)
|
const maxAge = cacheReq.getMaxAge(interceptOpt)
|
||||||
if (maxAge == null || maxAge <= 0) {
|
if (maxAge == null || maxAge <= 0) {
|
||||||
replaceHeaders['cache-control'] = '[remove]'
|
replaceHeaders['cache-control'] = REMOVE
|
||||||
replaceHeaders['last-modified'] = '[remove]'
|
replaceHeaders['last-modified'] = REMOVE
|
||||||
replaceHeaders.expires = '[remove]'
|
replaceHeaders.expires = REMOVE
|
||||||
}
|
}
|
||||||
|
|
||||||
actions += `${actions ? ',' : ''}download:${filename}`
|
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) {
|
function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDirect = false, target = null) {
|
||||||
// tunneling https
|
// tunneling https
|
||||||
// log.info('connect:', hostname, port)
|
// log.info('connect:', hostname, port)
|
||||||
const start = new Date()
|
const start = Date.now()
|
||||||
const isDnsIntercept = {}
|
const isDnsIntercept = {}
|
||||||
const hostport = `${hostname}:${port}`
|
const hostport = `${hostname}:${port}`
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
|
||||||
if (dnsConfig && dnsConfig.dnsMap) {
|
if (dnsConfig && dnsConfig.dnsMap) {
|
||||||
const dns = DnsUtil.hasDnsLookup(dnsConfig, hostname)
|
const dns = DnsUtil.hasDnsLookup(dnsConfig, hostname)
|
||||||
if (dns) {
|
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)
|
cltSocket.pipe(proxySocket)
|
||||||
})
|
})
|
||||||
proxySocket.on('timeout', () => {
|
proxySocket.on('timeout', () => {
|
||||||
const cost = new Date() - start
|
const cost = Date.now() - start
|
||||||
const errorMsg = `${isDirect ? '直连' : '代理连接'}超时: ${hostport}, cost: ${cost} ms`
|
const errorMsg = `${isDirect ? '直连' : '代理连接'}超时: ${hostport}, cost: ${cost} ms`
|
||||||
log.error(errorMsg)
|
log.error(errorMsg)
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
|
||||||
})
|
})
|
||||||
proxySocket.on('error', (e) => {
|
proxySocket.on('error', (e) => {
|
||||||
// 连接失败,可能被GFW拦截,或者服务端拥挤
|
// 连接失败,可能被GFW拦截,或者服务端拥挤
|
||||||
const cost = new Date() - start
|
const cost = Date.now() - start
|
||||||
const errorMsg = `${isDirect ? '直连' : '代理连接'}失败: ${hostport}, cost: ${cost} ms, errorMsg: ${e.message}`
|
const errorMsg = `${isDirect ? '直连' : '代理连接'}失败: ${hostport}, cost: ${cost} ms, errorMsg: ${e.message}`
|
||||||
log.error(`${errorMsg}\r\n`, e)
|
log.error(`${errorMsg}\r\n`, e)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,20 +109,22 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
|
||||||
|
|
||||||
function onFree () {
|
function onFree () {
|
||||||
url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}`
|
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))
|
log.info('发起代理请求:', url, (rOptions.servername ? `, sni: ${rOptions.servername}` : ''), ', headers:', jsonApi.stringify2(rOptions.headers))
|
||||||
|
|
||||||
const isDnsIntercept = {}
|
const isDnsIntercept = {}
|
||||||
if (dnsConfig && dnsConfig.dnsMap) {
|
if (dnsConfig && dnsConfig.dnsMap) {
|
||||||
let dns = DnsUtil.hasDnsLookup(dnsConfig, rOptions.hostname)
|
let dns = DnsUtil.hasDnsLookup(dnsConfig, rOptions.hostname)
|
||||||
if (!dns && rOptions.servername) {
|
if (!dns && rOptions.servername) {
|
||||||
dns = dnsConfig.dnsMap.quad9
|
dns = dnsConfig.dnsMap.ForSNI
|
||||||
if (dns) {
|
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) {
|
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}`)
|
log.debug(`域名 ${rOptions.hostname} DNS: ${dns.dnsName}`)
|
||||||
res.setHeader('DS-DNS', dns.dnsName)
|
res.setHeader('DS-DNS', dns.dnsName)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -154,7 +156,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyReq = (rOptions.protocol === 'https:' ? https : http).request(rOptions, (proxyRes) => {
|
proxyReq = (rOptions.protocol === 'https:' ? https : http).request(rOptions, (proxyRes) => {
|
||||||
const cost = new Date() - start
|
const cost = Date.now() - start
|
||||||
if (rOptions.protocol === 'https:') {
|
if (rOptions.protocol === 'https:') {
|
||||||
log.info(`代理请求返回: 【${proxyRes.statusCode}】${url}, cost: ${cost} ms`)
|
log.info(`代理请求返回: 【${proxyRes.statusCode}】${url}, cost: ${cost} ms`)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -171,7 +173,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
|
||||||
|
|
||||||
// 代理请求的事件监听
|
// 代理请求的事件监听
|
||||||
proxyReq.on('timeout', () => {
|
proxyReq.on('timeout', () => {
|
||||||
const cost = new Date() - start
|
const cost = Date.now() - start
|
||||||
const errorMsg = `代理请求超时: ${url}, cost: ${cost} ms`
|
const errorMsg = `代理请求超时: ${url}, cost: ${cost} ms`
|
||||||
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
|
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
|
||||||
countSlow(isDnsIntercept, `代理请求超时, cost: ${cost} ms`)
|
countSlow(isDnsIntercept, `代理请求超时, cost: ${cost} ms`)
|
||||||
|
|
@ -182,7 +184,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
proxyReq.on('error', (e) => {
|
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))
|
log.error(`代理请求错误: ${url}, cost: ${cost} ms, error:`, e, ', rOptions:', jsonApi.stringify2(rOptions))
|
||||||
countSlow(isDnsIntercept, `代理请求错误: ${e.message}`)
|
countSlow(isDnsIntercept, `代理请求错误: ${e.message}`)
|
||||||
reject(e)
|
reject(e)
|
||||||
|
|
@ -193,7 +195,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
proxyReq.on('aborted', () => {
|
proxyReq.on('aborted', () => {
|
||||||
const cost = new Date() - start
|
const cost = Date.now() - start
|
||||||
const errorMsg = `代理请求被取消: ${url}, cost: ${cost} ms`
|
const errorMsg = `代理请求被取消: ${url}, cost: ${cost} ms`
|
||||||
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
|
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
|
||||||
|
|
||||||
|
|
@ -209,7 +211,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
|
||||||
|
|
||||||
// 原始请求的事件监听
|
// 原始请求的事件监听
|
||||||
req.on('aborted', () => {
|
req.on('aborted', () => {
|
||||||
const cost = new Date() - start
|
const cost = Date.now() - start
|
||||||
const errorMsg = `请求被取消: ${url}, cost: ${cost} ms`
|
const errorMsg = `请求被取消: ${url}, cost: ${cost} ms`
|
||||||
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
|
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
|
||||||
proxyReq.abort()
|
proxyReq.abort()
|
||||||
|
|
@ -219,12 +221,12 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
|
||||||
reject(new Error(errorMsg))
|
reject(new Error(errorMsg))
|
||||||
})
|
})
|
||||||
req.on('error', (e, req, res) => {
|
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))
|
log.error(`请求错误: ${url}, cost: ${cost} ms, error:`, e, ', rOptions:', jsonApi.stringify2(rOptions))
|
||||||
reject(e)
|
reject(e)
|
||||||
})
|
})
|
||||||
req.on('timeout', () => {
|
req.on('timeout', () => {
|
||||||
const cost = new Date() - start
|
const cost = Date.now() - start
|
||||||
const errorMsg = `请求超时: ${url}, cost: ${cost} ms`
|
const errorMsg = `请求超时: ${url}, cost: ${cost} ms`
|
||||||
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
|
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
|
||||||
reject(new Error(errorMsg))
|
reject(new Error(errorMsg))
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,35 @@ const defaultDns = require('node:dns')
|
||||||
const log = require('../../../utils/util.log.server')
|
const log = require('../../../utils/util.log.server')
|
||||||
const speedTest = require('../../speed')
|
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 = {
|
module.exports = {
|
||||||
createLookupFunc (res, dns, action, target, isDnsIntercept) {
|
createLookupFunc (res, dns, action, target, port, isDnsIntercept) {
|
||||||
target = target ? (`, target: ${target}`) : ''
|
target = target ? (`, target: ${target}`) : ''
|
||||||
|
|
||||||
return (hostname, options, callback) => {
|
return (hostname, options, callback) => {
|
||||||
const tester = speedTest.getSpeedTester(hostname)
|
const tester = speedTest.getSpeedTester(hostname, port)
|
||||||
if (tester) {
|
if (tester) {
|
||||||
const aliveIpObj = tester.pickFastAliveIpObj()
|
const aliveIpObj = tester.pickFastAliveIpObj()
|
||||||
if (aliveIpObj) {
|
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)} }`)
|
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) {
|
if (isDnsIntercept) {
|
||||||
isDnsIntercept.dns = dns
|
isDnsIntercept.dns = dns
|
||||||
isDnsIntercept.hostname = hostname
|
isDnsIntercept.hostname = hostname
|
||||||
|
|
@ -29,35 +55,16 @@ module.exports = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ip !== hostname) {
|
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} -----`)
|
log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}${target} -----`)
|
||||||
if (res) {
|
if (res) {
|
||||||
res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`)
|
res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`)
|
||||||
}
|
}
|
||||||
callback(null, ip, 4)
|
callback(null, ip, 4)
|
||||||
return
|
|
||||||
} else {
|
} else {
|
||||||
// 使用默认dns
|
// 使用默认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 default DNS: ${hostname}${target}, options:`, options, ', dns:', dns)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 使用默认dns
|
|
||||||
log.info(`----- ${action}: ${hostname}, use hostname by 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 keys = pki.rsa.generateKeyPair(2048)
|
||||||
const cert = pki.createCertificate()
|
const cert = pki.createCertificate()
|
||||||
cert.publicKey = keys.publicKey
|
cert.publicKey = keys.publicKey
|
||||||
cert.serialNumber = `${(new Date()).getTime()}`
|
cert.serialNumber = `${Date.now()}`
|
||||||
cert.validity.notBefore = new Date(new Date() - (60 * 60 * 1000))
|
cert.validity.notBefore = new Date(Date.now() - (60 * 60 * 1000))
|
||||||
cert.validity.notAfter = new Date()
|
cert.validity.notAfter = new Date()
|
||||||
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 20)
|
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 20)
|
||||||
const attrs = [{
|
const attrs = [{
|
||||||
|
|
@ -87,7 +87,7 @@ utils.createFakeCertificateByDomain = function (caKey, caCert, domain, mappingHo
|
||||||
const cert = pki.createCertificate()
|
const cert = pki.createCertificate()
|
||||||
cert.publicKey = keys.publicKey
|
cert.publicKey = keys.publicKey
|
||||||
|
|
||||||
cert.serialNumber = `${(new Date()).getTime()}`
|
cert.serialNumber = `${Date.now()}`
|
||||||
cert.validity.notBefore = new Date()
|
cert.validity.notBefore = new Date()
|
||||||
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1)
|
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1)
|
||||||
cert.validity.notAfter = new Date()
|
cert.validity.notAfter = new Date()
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,44 @@
|
||||||
// 1个小时不访问,取消获取
|
// const { exec } = require('node:child_process')
|
||||||
const net = require('node:net')
|
const net = require('node:net')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const log = require('../../utils/util.log.server')
|
const log = require('../../utils/util.log.server')
|
||||||
const config = require('./config.js')
|
const config = require('./config.js')
|
||||||
|
|
||||||
|
// const isWindows = process.platform === 'win32'
|
||||||
|
|
||||||
const DISABLE_TIMEOUT = 60 * 60 * 1000
|
const DISABLE_TIMEOUT = 60 * 60 * 1000
|
||||||
|
|
||||||
class SpeedTester {
|
class SpeedTester {
|
||||||
constructor ({ hostname }) {
|
constructor ({ hostname, port }) {
|
||||||
this.dnsMap = config.getConfig().dnsMap
|
this.dnsMap = config.getConfig().dnsMap
|
||||||
|
|
||||||
this.hostname = hostname
|
this.hostname = hostname
|
||||||
this.lastReadTime = Date.now()
|
this.port = port || 443
|
||||||
|
|
||||||
this.ready = false
|
this.ready = false
|
||||||
this.alive = []
|
this.alive = []
|
||||||
this.backupList = []
|
this.backupList = []
|
||||||
this.keepCheckId = false
|
|
||||||
|
|
||||||
this.loadingIps = false
|
|
||||||
this.loadingTest = false
|
|
||||||
|
|
||||||
this.testCount = 0
|
this.testCount = 0
|
||||||
this.test()
|
this.lastReadTime = Date.now()
|
||||||
|
this.keepCheckIntervalId = false
|
||||||
|
|
||||||
|
this.tryTestCount = 0
|
||||||
|
|
||||||
|
this.test() // 异步:初始化完成后先测速一次
|
||||||
}
|
}
|
||||||
|
|
||||||
pickFastAliveIpObj () {
|
pickFastAliveIpObj () {
|
||||||
this.touch()
|
this.touch()
|
||||||
|
|
||||||
if (this.alive.length === 0) {
|
if (this.alive.length === 0) {
|
||||||
|
if (this.backupList.length > 0 && this.tryTestCount % 10 > 0) {
|
||||||
|
this.testBackups() // 异步
|
||||||
|
} else if (this.tryTestCount % 10 === 0) {
|
||||||
this.test() // 异步
|
this.test() // 异步
|
||||||
|
}
|
||||||
|
this.tryTestCount++
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return this.alive[0]
|
return this.alive[0]
|
||||||
|
|
@ -33,26 +46,27 @@ class SpeedTester {
|
||||||
|
|
||||||
touch () {
|
touch () {
|
||||||
this.lastReadTime = Date.now()
|
this.lastReadTime = Date.now()
|
||||||
if (!this.keepCheckId) {
|
if (!this.keepCheckIntervalId) {
|
||||||
this.startChecker()
|
this.startChecker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startChecker () {
|
startChecker () {
|
||||||
if (this.keepCheckId) {
|
if (this.keepCheckIntervalId) {
|
||||||
clearInterval(this.keepCheckId)
|
clearInterval(this.keepCheckIntervalId)
|
||||||
}
|
}
|
||||||
this.keepCheckId = setInterval(() => {
|
this.keepCheckIntervalId = setInterval(() => {
|
||||||
if (Date.now() - DISABLE_TIMEOUT > this.lastReadTime) {
|
if (Date.now() - DISABLE_TIMEOUT > this.lastReadTime) {
|
||||||
// 超过很长时间没有访问,取消测试
|
// 超过很长时间没有访问,取消测试
|
||||||
clearInterval(this.keepCheckId)
|
clearInterval(this.keepCheckIntervalId)
|
||||||
|
this.keepCheckIntervalId = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.alive.length > 0) {
|
if (this.alive.length > 0) {
|
||||||
this.testBackups()
|
this.testBackups() // 异步
|
||||||
return
|
} else {
|
||||||
|
this.test() // 异步
|
||||||
}
|
}
|
||||||
this.test()
|
|
||||||
}, config.getConfig().interval)
|
}, config.getConfig().interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,44 +85,56 @@ class SpeedTester {
|
||||||
promiseList.push(one)
|
promiseList.push(one)
|
||||||
}
|
}
|
||||||
await Promise.all(promiseList)
|
await Promise.all(promiseList)
|
||||||
|
|
||||||
const items = []
|
const items = []
|
||||||
for (const ip in ips) {
|
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
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFromOneDns (dns) {
|
async getFromOneDns (dns) {
|
||||||
return await dns._lookupInternal(this.hostname)
|
return await dns._lookupWithPreSetIpList(this.hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
async test () {
|
async test () {
|
||||||
|
this.testCount++
|
||||||
|
log.debug(`[speed] test start: ${this.hostname}, testCount: ${this.testCount}`)
|
||||||
|
|
||||||
|
try {
|
||||||
const newList = await this.getIpListFromDns(this.dnsMap)
|
const newList = await this.getIpListFromDns(this.dnsMap)
|
||||||
const newBackupList = [...newList, ...this.backupList]
|
const newBackupList = [...newList, ...this.backupList]
|
||||||
this.backupList = _.unionBy(newBackupList, 'host')
|
this.backupList = _.unionBy(newBackupList, 'host')
|
||||||
this.testCount++
|
|
||||||
|
|
||||||
log.info('[speed]', this.hostname, '➜ ip-list:', this.backupList)
|
|
||||||
await this.testBackups()
|
await this.testBackups()
|
||||||
|
log.info(`[speed] test end: ${this.hostname} ➜ ip-list:`, this.backupList, `, testCount: ${this.testCount}`)
|
||||||
if (config.notify) {
|
if (config.notify) {
|
||||||
config.notify({ key: 'test' })
|
config.notify({ key: 'test' })
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error(`[speed] test failed: ${this.hostname}, testCount: ${this.testCount}, error:`, e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async testBackups () {
|
async testBackups () {
|
||||||
const testAll = []
|
if (this.backupList.length > 0) {
|
||||||
const aliveList = []
|
const aliveList = []
|
||||||
|
|
||||||
|
const testAll = []
|
||||||
for (const item of this.backupList) {
|
for (const item of this.backupList) {
|
||||||
testAll.push(this.doTest(item, aliveList))
|
testAll.push(this.doTest(item, aliveList))
|
||||||
}
|
}
|
||||||
await Promise.all(testAll)
|
await Promise.all(testAll)
|
||||||
this.alive = aliveList
|
this.alive = aliveList
|
||||||
|
}
|
||||||
|
|
||||||
this.ready = true
|
this.ready = true
|
||||||
}
|
}
|
||||||
|
|
||||||
async doTest (item, aliveList) {
|
async doTest (item, aliveList) {
|
||||||
try {
|
try {
|
||||||
const ret = await this.testOne(item)
|
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)
|
_.merge(item, ret)
|
||||||
aliveList.push({ ...ret, ...item })
|
aliveList.push({ ...ret, ...item })
|
||||||
aliveList.sort((a, b) => a.time - b.time)
|
aliveList.sort((a, b) => a.time - b.time)
|
||||||
|
|
@ -125,48 +151,131 @@ class SpeedTester {
|
||||||
return a.time - b.time
|
return a.time - b.time
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message !== 'timeout') {
|
if (item.time == null) {
|
||||||
log.warn('[speed] test error: ', this.hostname, `➜ ${item.host}:${item.port} from DNS '${item.dns}'`, ', errorMsg:', e.message)
|
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) {
|
testByTCP (item) {
|
||||||
const timeout = 5000
|
|
||||||
const { host, port, dns } = item
|
|
||||||
const startTime = Date.now()
|
|
||||||
let isOver = false
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
const { host, dns } = item
|
||||||
|
const startTime = Date.now()
|
||||||
|
|
||||||
|
let isOver = false
|
||||||
|
const timeout = 5000
|
||||||
let timeoutId = null
|
let timeoutId = null
|
||||||
const client = net.createConnection({ host, port }, () => {
|
|
||||||
// 'connect' 监听器
|
const client = net.createConnection({ host, port: this.port }, () => {
|
||||||
const connectionTime = Date.now()
|
|
||||||
isOver = true
|
isOver = true
|
||||||
clearTimeout(timeoutId)
|
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.end()
|
||||||
})
|
})
|
||||||
client.on('end', () => {
|
|
||||||
})
|
|
||||||
client.on('error', (e) => {
|
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
|
isOver = true
|
||||||
clearTimeout(timeoutId)
|
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)
|
reject(e)
|
||||||
|
client.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
if (isOver) {
|
if (isOver) {
|
||||||
return
|
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'))
|
reject(new Error('timeout'))
|
||||||
client.end()
|
client.end()
|
||||||
}, timeout)
|
}, 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
|
module.exports = SpeedTester
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
const config = {
|
const config = {
|
||||||
notify () {},
|
|
||||||
dnsMap: {},
|
dnsMap: {},
|
||||||
}
|
}
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getConfig () {
|
getConfig () {
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
|
notify: null,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,28 @@ const SpeedTester = require('./SpeedTester.js')
|
||||||
const SpeedTestPool = {
|
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) {
|
function initSpeedTest (runtimeConfig) {
|
||||||
const { enabled, hostnameList } = runtimeConfig
|
const { enabled, hostnameList } = runtimeConfig
|
||||||
const conf = config.getConfig()
|
const conf = config.getConfig()
|
||||||
|
|
@ -14,45 +36,42 @@ function initSpeedTest (runtimeConfig) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_.forEach(hostnameList, (hostname) => {
|
_.forEach(hostnameList, (hostname) => {
|
||||||
SpeedTestPool[hostname] = new SpeedTester({ hostname })
|
addSpeedTest(hostname)
|
||||||
})
|
})
|
||||||
log.info('[speed] enabled')
|
log.info('[speed] enabled,SpeedTestPool:', SpeedTestPool)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllSpeedTester () {
|
function getAllSpeedTester () {
|
||||||
const allSpeed = {}
|
const allSpeed = {}
|
||||||
if (!config.getConfig().enabled) {
|
|
||||||
return allSpeed
|
if (config.getConfig().enabled) {
|
||||||
}
|
|
||||||
_.forEach(SpeedTestPool, (item, key) => {
|
_.forEach(SpeedTestPool, (item, key) => {
|
||||||
allSpeed[key] = {
|
allSpeed[key] = {
|
||||||
hostname: key,
|
hostname: item.hostname,
|
||||||
|
port: item.port,
|
||||||
alive: item.alive,
|
alive: item.alive,
|
||||||
backupList: item.backupList,
|
backupList: item.backupList,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return allSpeed
|
return allSpeed
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSpeedTester (hostname) {
|
function getSpeedTester (hostname, port) {
|
||||||
if (!config.getConfig().enabled) {
|
if (!config.getConfig().enabled) {
|
||||||
return
|
return null
|
||||||
}
|
}
|
||||||
let instance = SpeedTestPool[hostname]
|
return addSpeedTest(hostname, port)
|
||||||
if (instance == null) {
|
|
||||||
instance = new SpeedTester({ hostname })
|
|
||||||
SpeedTestPool[hostname] = instance
|
|
||||||
}
|
|
||||||
return instance
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerNotify (notify) {
|
// function registerNotify (notify) {
|
||||||
config.notify = notify
|
// config.notify = notify
|
||||||
}
|
// }
|
||||||
|
|
||||||
function reSpeedTest () {
|
function reSpeedTest () {
|
||||||
_.forEach(SpeedTestPool, (item, key) => {
|
_.forEach(SpeedTestPool, (item, _key) => {
|
||||||
item.test()
|
item.test() // 异步
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,8 +87,8 @@ module.exports = {
|
||||||
SpeedTester,
|
SpeedTester,
|
||||||
initSpeedTest,
|
initSpeedTest,
|
||||||
getSpeedTester,
|
getSpeedTester,
|
||||||
getAllSpeedTester,
|
// getAllSpeedTester,
|
||||||
registerNotify,
|
// registerNotify,
|
||||||
reSpeedTest,
|
reSpeedTest,
|
||||||
action,
|
action,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
const fs = require('node:fs')
|
const fs = require('node:fs')
|
||||||
const path = require('node:path')
|
const path = require('node:path')
|
||||||
const lodash = require('lodash')
|
const lodash = require('lodash')
|
||||||
const jsonApi = require('./json')
|
|
||||||
const dnsUtil = require('./lib/dns')
|
const dnsUtil = require('./lib/dns')
|
||||||
const interceptorImpls = require('./lib/interceptor')
|
const interceptorImpls = require('./lib/interceptor')
|
||||||
const scriptInterceptor = require('./lib/interceptor/impl/res/script')
|
const scriptInterceptor = require('./lib/interceptor/impl/res/script')
|
||||||
|
|
@ -110,7 +109,7 @@ module.exports = (serverConfig) => {
|
||||||
// 配置了白名单的域名,将跳过代理
|
// 配置了白名单的域名,将跳过代理
|
||||||
const inWhiteList = !!matchUtil.matchHostname(whiteList, hostname, 'in whiteList')
|
const inWhiteList = !!matchUtil.matchHostname(whiteList, hostname, 'in whiteList')
|
||||||
if (inWhiteList) {
|
if (inWhiteList) {
|
||||||
log.info(`为白名单域名,不拦截: ${hostname}, headers:`, jsonApi.stringify2(req.headers))
|
log.info(`为白名单域名,不拦截: ${hostname}`)
|
||||||
return false // 不拦截
|
return false // 不拦截
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,12 +186,12 @@ module.exports = (serverConfig) => {
|
||||||
if (impl.requestIntercept) {
|
if (impl.requestIntercept) {
|
||||||
// req拦截器
|
// req拦截器
|
||||||
interceptor.requestIntercept = (context, req, res, ssl, next) => {
|
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) {
|
} else if (impl.responseIntercept) {
|
||||||
// res拦截器
|
// res拦截器
|
||||||
interceptor.responseIntercept = (context, req, res, proxyReq, proxyRes, ssl, next) => {
|
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 lodash = require('lodash')
|
||||||
const log = require('./util.log.server')
|
const log = require('./util.log.server')
|
||||||
|
const mergeApi = require('@docmirror/dev-sidecar/src/merge')
|
||||||
|
|
||||||
function isMatched (url, regexp) {
|
function isMatched (url, regexp) {
|
||||||
if (regexp === '.*' || regexp === '*' || regexp === 'true' || regexp === true) {
|
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) {
|
function matchHostnameAll (hostMap, hostname, action) {
|
||||||
// log.debug('matchHostname-all:', action, hostMap)
|
// 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]
|
value = hostMap[regexp]
|
||||||
log.debug(`matchHostname-one: ${action}: '${hostname}' -> { "${regexp}": ${JSON.stringify(value)} }`)
|
log.debug(`matchHostname-one: ${action}: '${hostname}' -> { "${regexp}": ${JSON.stringify(value)} }`)
|
||||||
values = merge(values, 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)) {
|
if (!lodash.isEmpty(values)) {
|
||||||
deleteNullItems(values)
|
mergeApi.deleteNullItems(values)
|
||||||
log.info(`matchHostname-all: ${action}: '${hostname}':`, JSON.stringify(values))
|
log.info(`matchHostname-all: ${action}: '${hostname}':`, JSON.stringify(values))
|
||||||
return values
|
return values
|
||||||
} else {
|
} 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({
|
const dnsProviders = dns.initDNS({
|
||||||
// https
|
// 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: {
|
aliyun: {
|
||||||
type: 'https',
|
type: 'https',
|
||||||
server: 'https://dns.alidns.com/dns-query',
|
server: 'https://dns.alidns.com/dns-query',
|
||||||
|
|
@ -33,28 +25,10 @@ const dnsProviders = dns.initDNS({
|
||||||
safe360: {
|
safe360: {
|
||||||
server: 'https://doh.360.cn/dns-query',
|
server: 'https://doh.360.cn/dns-query',
|
||||||
cacheSize: 1000,
|
cacheSize: 1000,
|
||||||
},
|
forSNI: true,
|
||||||
rubyfish: {
|
|
||||||
server: 'https://rubyfish.cn/dns-query',
|
|
||||||
cacheSize: 1000,
|
|
||||||
},
|
|
||||||
py233: {
|
|
||||||
server: ' https://i.233py.com/dns-query',
|
|
||||||
cacheSize: 1000,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// tls
|
// 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: {
|
aliyunTLS: {
|
||||||
server: 'tls://223.5.5.5:853',
|
server: 'tls://223.5.5.5:853',
|
||||||
cacheSize: 1000,
|
cacheSize: 1000,
|
||||||
|
|
@ -93,7 +67,9 @@ const dnsProviders = dns.initDNS({
|
||||||
}, preSetIpList)
|
}, preSetIpList)
|
||||||
|
|
||||||
|
|
||||||
const presetHostname = 'xxx.com'
|
const hasPresetHostname = 'xxx.com'
|
||||||
|
const noPresetHostname = 'yyy.com'
|
||||||
|
|
||||||
const hostname1 = 'github.com'
|
const hostname1 = 'github.com'
|
||||||
const hostname2 = 'api.github.com'
|
const hostname2 = 'api.github.com'
|
||||||
const hostname3 = 'hk.docmirror.cn'
|
const hostname3 = 'hk.docmirror.cn'
|
||||||
|
|
@ -104,26 +80,36 @@ const hostname6 = 'gh2.docmirror.top'
|
||||||
let ip
|
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')
|
console.log('\n--------------- test PreSet ---------------\n')
|
||||||
ip = await dnsProviders.PreSet.lookup(presetHostname)
|
ip = await dnsProviders.PreSet.lookup(hasPresetHostname)
|
||||||
console.log('===> test PreSet:', ip, '\n\n')
|
console.log(`===> test PreSet: ${hasPresetHostname} ->`, ip, '\n\n')
|
||||||
console.log('\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')
|
console.log('\n--------------- test https ---------------\n')
|
||||||
ip = await dnsProviders.cloudflare.lookup(presetHostname)
|
ip = await dnsProviders.aliyun.lookup(hasPresetHostname)
|
||||||
assert.strictEqual(ip, presetIp) // test preset
|
assert.strictEqual(ip, presetIp) // test preset
|
||||||
console.log('\n\n')
|
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')
|
assert.strictEqual(dnsProviders.aliyun.dnsType, 'HTTPS')
|
||||||
ip = await dnsProviders.aliyun.lookup(hostname1)
|
ip = await dnsProviders.aliyun.lookup(hostname1)
|
||||||
console.log(`===> test aliyun: ${hostname1} ->`, ip, '\n\n')
|
console.log(`===> test aliyun: ${hostname1} ->`, ip, '\n\n')
|
||||||
|
|
@ -136,28 +122,12 @@ assert.strictEqual(dnsProviders.safe360.dnsType, 'HTTPS')
|
||||||
ip = await dnsProviders.safe360.lookup(hostname1)
|
ip = await dnsProviders.safe360.lookup(hostname1)
|
||||||
console.log(`===> test safe360: ${hostname1} ->`, ip, '\n\n')
|
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')
|
console.log('\n--------------- test TLS ---------------\n')
|
||||||
ip = await dnsProviders.cloudflareTLS.lookup(presetHostname)
|
ip = await dnsProviders.aliyunTLS.lookup(hasPresetHostname)
|
||||||
assert.strictEqual(ip, presetIp) // test preset
|
assert.strictEqual(ip, presetIp) // test preset
|
||||||
console.log('\n\n')
|
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')
|
assert.strictEqual(dnsProviders.aliyunTLS.dnsType, 'TLS')
|
||||||
ip = await dnsProviders.aliyunTLS.lookup(hostname1)
|
ip = await dnsProviders.aliyunTLS.lookup(hostname1)
|
||||||
console.log(`===> test aliyunTLS: ${hostname1} ->`, ip, '\n\n')
|
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')
|
console.log('\n--------------- test TCP ---------------\n')
|
||||||
ip = await dnsProviders.googleTCP.lookup(presetHostname)
|
ip = await dnsProviders.googleTCP.lookup(hasPresetHostname)
|
||||||
assert.strictEqual(ip, presetIp) // test preset
|
assert.strictEqual(ip, presetIp) // test preset
|
||||||
console.log('\n\n')
|
console.log('\n\n')
|
||||||
|
|
||||||
|
|
@ -186,7 +156,7 @@ console.log(`===> test aliyunTCP: ${hostname1} ->`, ip, '\n\n')
|
||||||
|
|
||||||
|
|
||||||
console.log('\n--------------- test UDP ---------------\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
|
assert.strictEqual(ip, presetIp) // test preset
|
||||||
console.log('\n\n')
|
console.log('\n\n')
|
||||||
|
|
||||||
|
|
@ -200,17 +170,13 @@ console.log(`===> test aliyunUDP: ${hostname1} ->`, ip, '\n\n')
|
||||||
|
|
||||||
dnsProviders.aliyunUDP.lookup(hostname1).then(ip0 => {
|
dnsProviders.aliyunUDP.lookup(hostname1).then(ip0 => {
|
||||||
console.log(`===> test aliyunUDP: ${hostname1} ->`, ip0, '\n\n')
|
console.log(`===> test aliyunUDP: ${hostname1} ->`, ip0, '\n\n')
|
||||||
assert.strictEqual(ip0, ip)
|
|
||||||
})
|
})
|
||||||
dnsProviders.aliyunUDP.lookup(hostname2).then(ip0 => {
|
dnsProviders.aliyunUDP.lookup(hostname2).then(ip0 => {
|
||||||
console.log(`===> test aliyunUDP: ${hostname2} ->`, ip0, '\n\n')
|
console.log(`===> test aliyunUDP: ${hostname2} ->`, ip0, '\n\n')
|
||||||
assert.notStrictEqual(ip0, ip)
|
|
||||||
})
|
})
|
||||||
dnsProviders.aliyunUDP.lookup('baidu.com').then(ip0 => {
|
dnsProviders.aliyunUDP.lookup('baidu.com').then(ip0 => {
|
||||||
console.log('===> test aliyunUDP: baidu.com ->', ip0, '\n\n')
|
console.log('===> test aliyunUDP: baidu.com ->', ip0, '\n\n')
|
||||||
assert.notStrictEqual(ip0, ip)
|
|
||||||
})
|
})
|
||||||
dnsProviders.aliyunUDP.lookup('gitee.com').then(ip0 => {
|
dnsProviders.aliyunUDP.lookup('gitee.com').then(ip0 => {
|
||||||
console.log('===> test aliyunUDP: gitee.com ->', ip0, '\n\n')
|
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(-1), false)
|
||||||
assert.strictEqual(isEmpty(''), false)
|
assert.strictEqual(isEmpty(''), false)
|
||||||
assert.strictEqual(isEmpty('1'), 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:
|
dns-over-http:
|
||||||
specifier: ^0.2.0
|
specifier: ^0.2.0
|
||||||
version: 0.2.0
|
version: 0.2.0
|
||||||
dns-over-tls:
|
|
||||||
specifier: ^0.0.9
|
|
||||||
version: 0.0.9
|
|
||||||
is-browser:
|
is-browser:
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
|
|
@ -3045,9 +3042,6 @@ packages:
|
||||||
dns-over-http@0.2.0:
|
dns-over-http@0.2.0:
|
||||||
resolution: {integrity: sha512-K+SyN2L3ljxJ2MFtOv/vRS+3/YEMLvOuH7MrmO5ejaubi4w02/DLqzoK1kBGKlQrT9ND57pbapeDf+ue8AElEA==}
|
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:
|
dns-packet@4.2.0:
|
||||||
resolution: {integrity: sha512-bn1AKpfkFbm0MIioOMHZ5qJzl2uypdBwI4nYNsqvhjsegBhcKJUlCrMPWLx6JEezRjxZmxhtIz/FkBEur2l8Cw==}
|
resolution: {integrity: sha512-bn1AKpfkFbm0MIioOMHZ5qJzl2uypdBwI4nYNsqvhjsegBhcKJUlCrMPWLx6JEezRjxZmxhtIz/FkBEur2l8Cw==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
@ -10553,10 +10547,6 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
dns-over-tls@0.0.9:
|
|
||||||
dependencies:
|
|
||||||
dns-packet: 5.6.1
|
|
||||||
|
|
||||||
dns-packet@4.2.0:
|
dns-packet@4.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ip: 1.1.9
|
ip: 1.1.9
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue