Merge branch 'docmirror:master' into only-edit-wiki

pull/493/head
Cute Omega 2025-09-13 23:02:46 +08:00 committed by GitHub
commit 38638e16d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 1043 additions and 297 deletions

View File

@ -393,6 +393,7 @@ const defaultConfig = {
type: 'https',
server: 'https://doh.360.cn/dns-query',
cacheSize: 1000,
forSNI: true,
},
rubyfish: {
type: 'https',

View File

@ -14,7 +14,7 @@ function parseVersion (version) {
* @param log 日志对象
* @returns {number} 比较线上版本号是否为更新的版本大于0=|0=相等|小于0=|-999=出现异常比较结果未知
*/
export function isNewVersion (onlineVersion, currentVersion, log = console) {
export function isNewVersion (onlineVersion, currentVersion, log = null) {
if (onlineVersion === currentVersion) {
return 0
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -410,7 +410,7 @@ function registerShowHideShortcut (showHideShortcut) {
function initApp () {
if (isMac) {
app.whenReady().then(() => {
app.dock.setIcon(path.join(__dirname, '../extra/icons/512x512.png'))
app.dock.setIcon(path.join(__dirname, '../extra/icons/512x512-2.png'))
})
}

View File

@ -1,3 +1,5 @@
let latestConfirmTime = null
function install (app, api) {
api.ipc.on('error.core', (event, message) => {
console.error('view on error', message)
@ -13,11 +15,17 @@ function install (app, api) {
function handleServerStartError (message, err, app, api) {
if (message.value === 'EADDRINUSE') {
// 避免重复弹窗
const now = Date.now()
if (latestConfirmTime != null && now - latestConfirmTime < 1000) {
return
}
latestConfirmTime = now
app.$confirm({
title: '端口被占用,代理服务启动失败',
content: '是否要杀掉占用进程?您也可以点击取消,然后前往加速服务->基本设置中修改代理端口',
onOk () {
// TODO 杀掉进程
api.config.get().then((config) => {
console.log('config:', config)
api.shell.killByPort({ port: config.server.port }).then((ret) => {

View File

@ -142,6 +142,19 @@ export default {
const dir = await this.$api.info.getLogDir()
this.$api.ipc.openPath(dir)
},
async focusFirst (ref) {
if (ref && ref.length != null) {
setTimeout(() => {
if (ref.length > 0) {
try {
ref[0].$el.querySelector('.ant-input').focus()
} catch (e) {
console.error('获取输入框焦点失败:', e)
}
}
}, 100)
}
},
handleHostname (hostname) {
if (this.isNotHostname(hostname)) {
return ''

View File

@ -50,6 +50,7 @@ export default {
},
addNoProxyUrl () {
this.noProxyUrls.unshift({ key: '' })
this.focusFirst(this.$refs.noProxyUrls)
},
delNoProxyUrl (item, index) {
this.noProxyUrls.splice(index, 1)
@ -108,7 +109,7 @@ export default {
<a-button type="primary" icon="plus" @click="addNoProxyUrl()" />
</a-col>
</a-row>
<a-row v-for="(item, index) of noProxyUrls" :key="index" :gutter="10">
<a-row v-for="(item, index) of noProxyUrls" ref="noProxyUrls" :key="index" :gutter="10">
<a-col :span="22">
<MockInput v-model="item.key" class="mt-2" />
</a-col>

View File

@ -60,6 +60,7 @@ export default {
},
addTarget () {
this.targets.unshift({ key: '', value: 'true' })
this.focusFirst(this.$refs.targets)
},
deleteTarget (item, index) {
this.targets.splice(index, 1)
@ -88,14 +89,17 @@ export default {
})
}
if (this.servers.length === 0) {
this.addServer()
this.addServer(false)
}
},
deleteServer (item, index) {
this.servers.splice(index, 1)
},
addServer () {
addServer (needFocus = true) {
this.servers.unshift({ key: '', value: { type: 'path' } })
if (needFocus) {
this.focusFirst(this.$refs.servers)
}
},
submitServer () {
const map = {}
@ -169,7 +173,7 @@ export default {
<a-button type="primary" icon="plus" @click="addTarget()" />
</a-col>
</a-row>
<a-row v-for="(item, index) of targets" :key="index" :gutter="10">
<a-row v-for="(item, index) of targets" ref="targets" :key="index" :gutter="10">
<a-col :span="18">
<MockInput v-model="item.key" class="mt-2" />
</a-col>
@ -196,7 +200,7 @@ export default {
<a-button type="primary" icon="plus" @click="addServer()" />
</a-col>
</a-row>
<a-row v-for="(item, index) of servers" :key="index" :gutter="10">
<a-row v-for="(item, index) of servers" ref="servers" :key="index" :gutter="10">
<a-col :span="6">
<a-input v-model="item.key" :title="item.key" addon-before="" placeholder="yourdomain.com" spellcheck="false" />
</a-col>

View File

@ -62,6 +62,7 @@ export default {
},
addExcludeIp () {
this.excludeIpList.unshift({ key: '', value: 'true' })
this.focusFirst(this.$refs.excludeIpList)
},
delExcludeIp (item, index) {
this.excludeIpList.splice(index, 1)
@ -166,7 +167,7 @@ export default {
<a-button type="primary" icon="plus" @click="addExcludeIp()" />
</a-col>
</a-row>
<a-row v-for="(item, index) of excludeIpList" :key="index" :gutter="10" class="fine-tuning">
<a-row v-for="(item, index) of excludeIpList" ref="excludeIpList" :key="index" :gutter="10" class="fine-tuning">
<a-col :span="17">
<MockInput v-model="item.key" class="mt-1" />
</a-col>

View File

@ -108,6 +108,7 @@ export default {
},
addDnsMapping () {
this.dnsMappings.unshift({ key: '', value: 'quad9' })
this.focusFirst(this.$refs.dnsMappings)
},
// whiteList
@ -123,6 +124,7 @@ export default {
},
addWhiteList () {
this.whiteList.unshift({ key: '', value: 'true' })
this.focusFirst(this.$refs.whiteList)
},
deleteWhiteList (item, index) {
this.whiteList.splice(index, 1)
@ -144,6 +146,7 @@ export default {
},
addSpeedHostname () {
this.getSpeedTestConfig().hostnameList.unshift('')
this.focusFirst(this.$refs.hostnameList)
},
delSpeedHostname (item, index) {
this.getSpeedTestConfig().hostnameList.splice(index, 1)
@ -310,13 +313,13 @@ export default {
<div v-if="activeTabKey === '4'">
<a-row style="margin-top:10px">
<a-col span="21">
<div>这里配置的域名不会通过代理</div>
<div>配置<code>不代理</code>的域名不会通过代理</div>
</a-col>
<a-col span="3">
<a-button style="margin-left:8px" type="primary" icon="plus" @click="addWhiteList()" />
</a-col>
</a-row>
<a-row v-for="(item, index) of whiteList" :key="index" :gutter="10" style="margin-top: 5px">
<a-row v-for="(item, index) of whiteList" ref="whiteList" :key="index" :gutter="10" style="margin-top: 5px">
<a-col :span="16">
<MockInput v-model="item.key" />
</a-col>
@ -374,7 +377,7 @@ export default {
<a-button style="margin-left:8px" type="primary" icon="plus" @click="addDnsMapping()" />
</a-col>
</a-row>
<a-row v-for="(item, index) of dnsMappings" :key="index" :gutter="10" style="margin-top: 5px">
<a-row v-for="(item, index) of dnsMappings" ref="dnsMappings" :key="index" :gutter="10" style="margin-top: 5px">
<a-col :span="15">
<MockInput v-model="item.key" />
</a-col>
@ -422,7 +425,7 @@ export default {
<a-button style="margin-left:10px" type="primary" icon="plus" @click="addSpeedHostname()" />
</a-col>
</a-row>
<a-row v-for="(item, index) of getSpeedTestConfig().hostnameList" :key="index" :gutter="10" style="margin-top: 5px">
<a-row v-for="(item, index) of getSpeedTestConfig().hostnameList" ref="hostnameList" :key="index" :gutter="10" style="margin-top: 5px">
<a-col :span="21">
<MockInput v-model="getSpeedTestConfig().hostnameList[index]" />
</a-col>
@ -452,9 +455,9 @@ export default {
</a>
<a-tag
v-for="(element, index) of item.backupList" :key="index" style="margin:2px;"
:title="element.dns" :color="element.time ? (element.time > config.server.setting.lowSpeedDelay ? 'orange' : 'green') : 'red'"
:title="element.title || `测速中:${element.host}`" :color="element.time ? (element.time > config.server.setting.lowSpeedDelay ? 'orange' : 'green') : (element.title ? 'red' : '')"
>
{{ element.host }} {{ element.time }}{{ element.time ? 'ms' : '' }} {{ element.dns }}
{{ element.host }} {{ element.time ? `${element.time}ms` : (element.title ? '' : '测速中') }} {{ element.dns }}
</a-tag>
</a-card>
</a-col>

View File

@ -152,6 +152,12 @@ $dark-input: #777; //输入框:背景色
border-color: #5a5750;
color: #cfa572;
}
/* 标签:未知 */
.ant-tag:not(.ant-tag-red, .ant-tag-green, .ant-tag-orange) {
background-color: #5a5a5a;
border-color: #5a5a5a;
color: #ccc;
}
/* 按钮 */
.ant-btn:not(.ant-btn-danger, .ant-btn-primary) {

View File

@ -18,7 +18,6 @@
"axios": "^1.7.7",
"baidu-aip-sdk": "^4.16.16",
"dns-over-http": "^0.2.0",
"dns-over-tls": "^0.0.9",
"is-browser": "^2.1.0",
"json5": "^2.2.3",
"lodash": "^4.17.21",

View File

@ -52,21 +52,26 @@ module.exports = class BaseDNS {
}
}
async lookup (hostname) {
async lookup (hostname, ipChecker) {
try {
let ipCache = this.cache.get(hostname)
if (ipCache) {
if (ipCache.value != null) {
ipCache.doCount(ipCache.value, false)
return ipCache.value
const ip = ipCache.value
if (ip != null) {
if (ipChecker && ipChecker(ip)) {
ipCache.doCount(ip, false)
return ip
} else {
return hostname
}
}
} else {
ipCache = new IpCache(hostname)
this.cache.set(hostname, ipCache)
}
const t = new Date()
let ipList = await this._lookupInternal(hostname)
const t = Date.now()
let ipList = await this._lookupWithPreSetIpList(hostname)
if (ipList == null) {
// 没有获取到ipv4地址
ipList = []
@ -74,29 +79,45 @@ module.exports = class BaseDNS {
ipList.push(hostname) // 把原域名加入到统计里去
ipCache.setBackupList(ipList)
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] ${hostname}${ipCache.value} (${new Date() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache))
return ipCache.value
const ip = ipCache.value
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] ${hostname}${ip} (${Date.now() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache))
if (ipChecker) {
if (ip != null && ip !== hostname && ipChecker(ip)) {
return ip
}
for (const ip of ipList) {
if (ip !== hostname && ipChecker(ip)) {
return ip
}
}
}
return ip != null ? ip : hostname
} catch (error) {
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] cannot resolve hostname ${hostname}, error:`, error)
return hostname
}
}
async _lookupInternal (hostname) {
// 获取当前域名的预设IP列表
let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, `matched preSetIpList(${this.dnsName})`)
if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) {
if (hostnamePreSetIpList.length > 0) {
hostnamePreSetIpList = hostnamePreSetIpList.slice()
} else {
hostnamePreSetIpList = mapToList(hostnamePreSetIpList)
}
async _lookupWithPreSetIpList (hostname) {
if (this.preSetIpList) {
// 获取当前域名的预设IP列表
let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, `matched preSetIpList(${this.dnsName})`)
if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) {
if (hostnamePreSetIpList.length > 0) {
hostnamePreSetIpList = hostnamePreSetIpList.slice() // 复制一份列表数据,避免配置数据被覆盖
} else {
hostnamePreSetIpList = mapToList(hostnamePreSetIpList)
}
if (hostnamePreSetIpList.length > 0) {
hostnamePreSetIpList.isPreSet = true
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的预设IP列表 ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`)
return hostnamePreSetIpList
if (hostnamePreSetIpList.length > 0) {
hostnamePreSetIpList.isPreSet = true
log.info(`[DNS-over-PreSet '${this.dnsName}'] 获取到该域名的预设IP列表 ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`)
return hostnamePreSetIpList
}
}
}
@ -105,24 +126,79 @@ module.exports = class BaseDNS {
async _lookup (hostname) {
const start = Date.now()
let response
try {
// 执行DNS查询
log.debug(`[DNS-over-${this.dnsType} '${this.dnsName}'] query start: ${hostname}`)
response = await this._doDnsQuery(hostname, 'A', start)
} catch {
// 异常日志在 _doDnsQuery已经打印过这里就不再打印了
return []
}
try {
const response = await this._doDnsQuery(hostname)
const cost = Date.now() - start
log.debug(`[DNS-over-${this.dnsType} '${this.dnsName}'] query end: ${hostname}, cost: ${cost} ms, response:`, response)
if (response == null || response.answers == null || response.answers.length == null || response.answers.length === 0) {
// 说明没有获取到ip
log.warn(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IP地址: ${hostname}, cost: ${cost} ms, response:`, response)
return []
}
const ret = response.answers.filter(item => item.type === 'A').map(item => item.data)
if (ret.length === 0) {
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IPv4地址: ${hostname}, cost: ${cost} ms`)
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IP地址: ${hostname}, cost: ${cost} ms`)
} else {
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IPv4地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`)
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IP地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`)
}
return ret
} catch (e) {
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS query error, hostname: ${hostname}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}, cost: ${Date.now() - start} ms, error:`, e)
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] 解读响应失败response:`, response, ', error:', e)
return []
}
}
_doDnsQuery (hostname, type = 'A', start) {
if (start == null) {
start = Date.now()
}
return new Promise((resolve, reject) => {
// 设置超时任务
let isOver = false
const timeout = 8000
const timeoutId = setTimeout(() => {
if (!isOver) {
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS查询超时, hostname: ${hostname}, sni: ${this.dnsServerName || '无'}, type: ${type}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}${this.dnsServerPort ? `:${this.dnsServerPort}` : ''}, cost: ${Date.now() - start} ms`)
reject(new Error('DNS查询超时'))
}
}, timeout)
try {
this._dnsQueryPromise(hostname, type)
.then((response) => {
isOver = true
clearTimeout(timeoutId)
resolve(response)
})
.catch((e) => {
isOver = true
clearTimeout(timeoutId)
if (e.message === 'DNS查询超时') {
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS查询超时. hostname: ${hostname}, sni: ${this.dnsServerName || '无'}, type: ${type}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}${this.dnsServerPort ? `:${this.dnsServerPort}` : ''}, cost: ${Date.now() - start} ms`)
} else {
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS查询错误, hostname: ${hostname}, sni: ${this.dnsServerName || '无'}, type: ${type}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}${this.dnsServerPort ? `:${this.dnsServerPort}` : ''}, cost: ${Date.now() - start} ms, error:`, e)
}
reject(e)
})
} catch (e) {
isOver = true
clearTimeout(timeoutId)
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS查询异常, hostname: ${hostname}, type: ${type}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}${this.dnsServerPort ? `:${this.dnsServerPort}` : ''}, cost: ${Date.now() - start} ms, error:`, e)
reject(e)
}
})
}
}

View File

@ -1,16 +1,45 @@
const { promisify } = require('node:util')
const doh = require('dns-over-http')
const BaseDNS = require('./base')
const HttpsAgent = require('../proxy/common/ProxyHttpsAgent')
const Agent = require('../proxy/common/ProxyHttpAgent')
const dohQueryAsync = promisify(doh.query)
function createAgent (dnsServer) {
return new (dnsServer.startsWith('https:') ? HttpsAgent : Agent)({
keepAlive: true,
timeout: 4000,
})
}
module.exports = class DNSOverHTTPS extends BaseDNS {
constructor (dnsName, cacheSize, preSetIpList, dnsServer) {
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerName) {
super(dnsName, 'HTTPS', cacheSize, preSetIpList)
this.dnsServer = dnsServer
this.dnsServer = dnsServer.replace(/\s+/, '')
this.dnsServerName = dnsServerName
}
async _doDnsQuery (hostname) {
return await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }])
_dnsQueryPromise (hostname, type = 'A') {
// 请求参数
const options = {
url: this.dnsServer,
agent: createAgent(this.dnsServer),
}
if (this.dnsServerName) {
// 设置SNI
options.servername = this.dnsServerName
options.rejectUnauthorized = false
}
// DNS查询参数
const questions = [
{
type,
name: hostname,
},
]
return dohQueryAsync(options, questions)
}
}

View File

@ -1,4 +1,5 @@
const matchUtil = require('../../utils/util.match')
const log = require('../../utils/util.log.server')
const DNSOverPreSetIpList = require('./preset.js')
const DNSOverHTTPS = require('./https.js')
const DNSOverTLS = require('./tls.js')
@ -27,7 +28,7 @@ module.exports = {
if (type == null) {
if (server.startsWith('https://') || server.startsWith('http://')) {
type = 'https'
} else if (server.startsWith('tls://')) {
} else if (server.startsWith('tls://') || server.startsWith('dot://')) {
type = 'tls'
} else if (server.startsWith('tcp://')) {
type = 'tcp'
@ -47,7 +48,7 @@ module.exports = {
}
// 基于 https
dnsMap[provider] = new DNSOverHTTPS(provider, conf.cacheSize, preSetIpList, server)
dnsMap[provider] = new DNSOverHTTPS(provider, conf.cacheSize, preSetIpList, server, conf.sni || conf.servername)
} else {
// 获取DNS端口
let port = conf.port
@ -63,8 +64,8 @@ module.exports = {
if (type === 'tls' || type === 'dot' || type === 'dns-over-tls') {
// 基于 tls
dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.servername)
} else if (type === 'tcp' || type === 'dns-over-tcp') {
dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.sni || conf.servername)
} else if (type === 'tcp') {
// 基于 tcp
dnsMap[provider] = new DNSOverTCP(provider, conf.cacheSize, preSetIpList, server, port)
} else {
@ -72,10 +73,19 @@ module.exports = {
dnsMap[provider] = new DNSOverUDP(provider, conf.cacheSize, preSetIpList, server, port)
}
}
if (conf.forSNI || conf.forSni) {
dnsMap.ForSNI = dnsMap[provider]
}
}
// 创建预设IP的DNS
dnsMap.PreSet = new DNSOverPreSetIpList(preSetIpList)
if (dnsMap.ForSNI == null) {
dnsMap.ForSNI = dnsMap.PreSet
}
log.info(`设置SNI默认使用的DNS为 '${dnsMap.ForSNI.dnsName}'当某个域名配置了SNI但未配置DNS时将默认使用该DNS`)
return dnsMap
},

View File

@ -4,16 +4,16 @@ const dnsPacket = require('dns-packet')
const randi = require('random-int')
const BaseDNS = require('./base')
const defaultPort = 53 // UDP类型的DNS服务默认端口号
const defaultPort = 53 // TCP类型的DNS服务默认端口号
module.exports = class DNSOverTCP extends BaseDNS {
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) {
super(dnsName, 'TCP', cacheSize, preSetIpList)
this.dnsServer = dnsServer
this.dnsServer = dnsServer.replace(/\s+/, '')
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
}
_doDnsQuery (hostname) {
_dnsQueryPromise (hostname, type = 'A') {
return new Promise((resolve, reject) => {
// 构造 DNS 查询报文
const packet = dnsPacket.encode({
@ -21,7 +21,7 @@ module.exports = class DNSOverTCP extends BaseDNS {
type: 'query',
id: randi(0x0, 0xFFFF),
questions: [{
type: 'A',
type,
name: hostname,
}],
})

View File

@ -1,4 +1,4 @@
const dnstls = require('dns-over-tls')
const dnstls = require('./util/dns-over-tls')
const BaseDNS = require('./base')
const defaultPort = 853
@ -6,22 +6,25 @@ const defaultPort = 853
module.exports = class DNSOverTLS extends BaseDNS {
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort, dnsServerName) {
super(dnsName, 'TLS', cacheSize, preSetIpList)
this.dnsServer = dnsServer
this.dnsServer = dnsServer.replace(/\s+/, '')
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
this.dnsServerName = dnsServerName
}
async _doDnsQuery (hostname) {
_dnsQueryPromise (hostname, type = 'A') {
const options = {
host: this.dnsServer,
port: this.dnsServerPort,
servername: this.dnsServerName || this.dnsServer,
rejectUnauthorized: !this.dnsServerName,
name: hostname,
klass: 'IN',
type: 'A',
type,
timeout: 4000,
}
return await dnstls.query(options)
return dnstls.query(options)
}
}

View File

@ -8,28 +8,35 @@ const defaultPort = 53 // UDP类型的DNS服务默认端口号
module.exports = class DNSOverUDP extends BaseDNS {
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) {
super(dnsName, 'UDP', cacheSize, preSetIpList)
this.dnsServer = dnsServer
this.dnsServer = dnsServer.replace(/\s+/, '')
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
this.isIPv6 = dnsServer.includes(':') && dnsServer.includes('[') && dnsServer.includes(']')
this.socketType = this.isIPv6 ? 'udp6' : 'udp4'
}
_doDnsQuery (hostname) {
_dnsQueryPromise (hostname, type = 'A') {
return new Promise((resolve, reject) => {
let isOver = false
const timeout = 5000
let timeoutId = null
// 构造 DNS 查询报文
const packet = dnsPacket.encode({
flags: dnsPacket.RECURSION_DESIRED,
type: 'query',
id: randi(0x0, 0xFFFF),
questions: [{
type: 'A',
type,
name: hostname,
}],
})
// 创建客户端
const udpClient = dgram.createSocket(this.socketType, (msg, _rinfo) => {
isOver = true
clearTimeout(timeoutId)
const response = dnsPacket.decode(msg)
resolve(response)
udpClient.close()
@ -38,10 +45,20 @@ module.exports = class DNSOverUDP extends BaseDNS {
// 发送 UDP 查询
udpClient.send(packet, 0, packet.length, this.dnsServerPort, this.dnsServer, (err, _bytes) => {
if (err) {
isOver = true
clearTimeout(timeoutId)
reject(err)
udpClient.close()
}
})
// 设置超时任务
timeoutId = setTimeout(() => {
if (!isOver) {
reject(new Error('DNS查询超时'))
udpClient.close()
}
}, timeout)
})
}
}

View File

@ -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 }

View File

@ -5,10 +5,18 @@ module.exports = {
const { rOptions, log } = context
if (interceptOpt.abort === true || interceptOpt.abort === 'true') {
res.writeHead(403, {
const headers = {
'Content-Type': 'text/plain; charset=utf-8',
'DS-Interceptor': 'abort',
})
}
// headers.Access-Control-Allow-*:避免跨域问题
if (rOptions.headers.origin) {
headers['Access-Control-Allow-Credentials'] = 'true'
headers['Access-Control-Allow-Origin'] = rOptions.headers.origin
}
res.writeHead(403, headers)
res.write(
'DevSidecar 403: Request abort.\n\n'
+ ' This request is matched by abort intercept.\n\n'

View File

@ -111,7 +111,7 @@ function checkIsLimitConfig (id, api) {
module.exports = {
name: 'baiduOcr',
priority: 131,
requestIntercept (context, interceptOpt, req, res, ssl, next, matched) {
requestIntercept (context, interceptOpt, req, res, ssl, next) {
const { rOptions, log } = context
const headers = {

View File

@ -1,16 +1,29 @@
const url = require('node:url')
const lodash = require('lodash')
function replacePlaceholder0 (url, matched, pre) {
if (matched) {
for (let i = 0; i < matched.length; i++) {
url = url.replace(`\${${pre}[${i}]}`, matched[i] || '')
}
if (matched.groups) {
for (const key in matched.groups) {
url = url.replace(`\${${key}}`, matched.groups[key] || '')
}
}
}
return url
}
// 替换占位符
function replacePlaceholder (url, rOptions, matched) {
function replacePlaceholder (url, rOptions, pathMatched, hostnameMatched) {
if (url.includes('${')) {
// eslint-disable-next-line no-template-curly-in-string
url = url.replace('${host}', rOptions.hostname)
if (matched && url.includes('${')) {
for (let i = 0; i < matched.length; i++) {
url = url.replace(`\${m[${i}]}`, matched[i] == null ? '' : matched[i])
}
if (url.includes('${')) {
url = replacePlaceholder0(url, pathMatched, 'p')
url = replacePlaceholder0(url, hostnameMatched, 'h')
}
// 移除多余的占位符
@ -22,7 +35,7 @@ function replacePlaceholder (url, rOptions, matched) {
return url
}
function buildTargetUrl (rOptions, urlConf, interceptOpt, matched) {
function buildTargetUrl (rOptions, urlConf, interceptOpt, matched, hostnameMatched) {
let targetUrl
if (interceptOpt && interceptOpt.replace) {
const regexp = new RegExp(interceptOpt.replace)
@ -40,7 +53,7 @@ function buildTargetUrl (rOptions, urlConf, interceptOpt, matched) {
}
// 替换占位符
targetUrl = replacePlaceholder(targetUrl, rOptions, matched)
targetUrl = replacePlaceholder(targetUrl, rOptions, matched, hostnameMatched)
// 拼接协议
targetUrl = targetUrl.indexOf('http:') === 0 || targetUrl.indexOf('https:') === 0 ? targetUrl : `${rOptions.protocol}//${targetUrl}`
@ -48,9 +61,9 @@ function buildTargetUrl (rOptions, urlConf, interceptOpt, matched) {
return targetUrl
}
function doProxy (proxyConf, rOptions, req, interceptOpt, matched) {
function doProxy (proxyConf, rOptions, req, interceptOpt, matched, hostnameMatched) {
// 获取代理目标地址
const proxyTarget = buildTargetUrl(rOptions, proxyConf, interceptOpt, matched)
const proxyTarget = buildTargetUrl(rOptions, proxyConf, interceptOpt, matched, hostnameMatched)
// 替换rOptions的属性
// eslint-disable-next-line node/no-deprecated-api
@ -76,7 +89,7 @@ module.exports = {
replacePlaceholder,
buildTargetUrl,
doProxy,
requestIntercept (context, interceptOpt, req, res, ssl, next, matched) {
requestIntercept (context, interceptOpt, req, res, ssl, next, matched, hostnameMatched) {
const { rOptions, log, RequestCounter } = context
const originHostname = rOptions.hostname
@ -107,7 +120,7 @@ module.exports = {
}
// 替换 rOptions 中的地址,并返回代理目标地址
const proxyTarget = doProxy(proxyConf, rOptions, req, interceptOpt, matched)
const proxyTarget = doProxy(proxyConf, rOptions, req, interceptOpt, matched, hostnameMatched)
if (context.requestCount) {
log.info('proxy choice:', JSON.stringify(context.requestCount))

View File

@ -3,16 +3,24 @@ const proxyApi = require('./proxy')
module.exports = {
name: 'redirect',
priority: 105,
requestIntercept (context, interceptOpt, req, res, ssl, next, matched) {
requestIntercept (context, interceptOpt, req, res, ssl, next, matched, hostnameMatched) {
const { rOptions, log } = context
// 获取重定向目标地址
const redirect = proxyApi.buildTargetUrl(rOptions, interceptOpt.redirect, interceptOpt, matched)
const redirect = proxyApi.buildTargetUrl(rOptions, interceptOpt.redirect, interceptOpt, matched, hostnameMatched)
res.writeHead(302, {
const headers = {
'Location': redirect,
'DS-Interceptor': 'redirect',
})
}
// headers.Access-Control-Allow-*:避免跨域问题
if (rOptions.headers.origin) {
headers['Access-Control-Allow-Credentials'] = 'true'
headers['Access-Control-Allow-Origin'] = rOptions.headers.origin
}
res.writeHead(302, headers)
res.end()
const url = `${rOptions.method}${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}`

View File

@ -5,10 +5,18 @@ module.exports = {
const { rOptions, log } = context
if (interceptOpt.success === true || interceptOpt.success === 'true') {
res.writeHead(200, {
const headers = {
'Content-Type': 'text/plain; charset=utf-8',
'DS-Interceptor': 'success',
})
}
// headers.Access-Control-Allow-*:避免跨域问题
if (rOptions.headers.origin) {
headers['Access-Control-Allow-Credentials'] = 'true'
headers['Access-Control-Allow-Origin'] = rOptions.headers.origin
}
res.writeHead(200, headers)
res.write(
'DevSidecar 200: Request success.\n\n'
+ ' This request is matched by success intercept.\n\n'

View File

@ -92,9 +92,9 @@ module.exports = {
// 如果未手动配置需要缓存,则不允许使用缓存
const maxAge = cacheReq.getMaxAge(interceptOpt)
if (maxAge == null || maxAge <= 0) {
replaceHeaders['cache-control'] = '[remove]'
replaceHeaders['last-modified'] = '[remove]'
replaceHeaders.expires = '[remove]'
replaceHeaders['cache-control'] = REMOVE
replaceHeaders['last-modified'] = REMOVE
replaceHeaders.expires = REMOVE
}
actions += `${actions ? ',' : ''}download:${filename}`

View File

@ -55,7 +55,7 @@ module.exports = function createConnectHandler (sslConnectInterceptor, middlewar
function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDirect = false, target = null) {
// tunneling https
// log.info('connect:', hostname, port)
const start = new Date()
const start = Date.now()
const isDnsIntercept = {}
const hostport = `${hostname}:${port}`
@ -114,7 +114,7 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
if (dnsConfig && dnsConfig.dnsMap) {
const dns = DnsUtil.hasDnsLookup(dnsConfig, hostname)
if (dns) {
options.lookup = dnsLookup.createLookupFunc(null, dns, 'connect', hostport, isDnsIntercept)
options.lookup = dnsLookup.createLookupFunc(null, dns, 'connect', hostport, port, isDnsIntercept)
}
}
// 代理连接事件监听
@ -134,7 +134,7 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
cltSocket.pipe(proxySocket)
})
proxySocket.on('timeout', () => {
const cost = new Date() - start
const cost = Date.now() - start
const errorMsg = `${isDirect ? '直连' : '代理连接'}超时: ${hostport}, cost: ${cost} ms`
log.error(errorMsg)
@ -148,7 +148,7 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
})
proxySocket.on('error', (e) => {
// 连接失败可能被GFW拦截或者服务端拥挤
const cost = new Date() - start
const cost = Date.now() - start
const errorMsg = `${isDirect ? '直连' : '代理连接'}失败: ${hostport}, cost: ${cost} ms, errorMsg: ${e.message}`
log.error(`${errorMsg}\r\n`, e)

View File

@ -109,20 +109,22 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
function onFree () {
url = `${rOptions.method}${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}`
const start = new Date()
const start = Date.now()
log.info('发起代理请求:', url, (rOptions.servername ? `, sni: ${rOptions.servername}` : ''), ', headers:', jsonApi.stringify2(rOptions.headers))
const isDnsIntercept = {}
if (dnsConfig && dnsConfig.dnsMap) {
let dns = DnsUtil.hasDnsLookup(dnsConfig, rOptions.hostname)
if (!dns && rOptions.servername) {
dns = dnsConfig.dnsMap.quad9
dns = dnsConfig.dnsMap.ForSNI
if (dns) {
log.info(`域名 ${rOptions.hostname} 在dns中未配置但使用了 sni: ${rOptions.servername}, 必须使用dns现默认使用 'quad9' DNS.`)
log.info(`域名 ${rOptions.hostname} 在dns中未配置但使用了 sni: ${rOptions.servername}, 必须使用dns现默认使用 '${dns.dnsName}' DNS.`)
} else {
log.warn(`域名 ${rOptions.hostname} 在dns中未配置但使用了 sni: ${rOptions.servername}且DNS服务管理中也未指定SNI默认使用的DNS。`)
}
}
if (dns) {
rOptions.lookup = dnsLookup.createLookupFunc(res, dns, 'request url', url, isDnsIntercept)
rOptions.lookup = dnsLookup.createLookupFunc(res, dns, 'request url', url, rOptions.port, isDnsIntercept)
log.debug(`域名 ${rOptions.hostname} DNS: ${dns.dnsName}`)
res.setHeader('DS-DNS', dns.dnsName)
} else {
@ -154,7 +156,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
}
proxyReq = (rOptions.protocol === 'https:' ? https : http).request(rOptions, (proxyRes) => {
const cost = new Date() - start
const cost = Date.now() - start
if (rOptions.protocol === 'https:') {
log.info(`代理请求返回: 【${proxyRes.statusCode}${url}, cost: ${cost} ms`)
} else {
@ -171,7 +173,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
// 代理请求的事件监听
proxyReq.on('timeout', () => {
const cost = new Date() - start
const cost = Date.now() - start
const errorMsg = `代理请求超时: ${url}, cost: ${cost} ms`
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
countSlow(isDnsIntercept, `代理请求超时, cost: ${cost} ms`)
@ -182,7 +184,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
reject(error)
})
proxyReq.on('error', (e) => {
const cost = new Date() - start
const cost = Date.now() - start
log.error(`代理请求错误: ${url}, cost: ${cost} ms, error:`, e, ', rOptions:', jsonApi.stringify2(rOptions))
countSlow(isDnsIntercept, `代理请求错误: ${e.message}`)
reject(e)
@ -193,7 +195,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
}
})
proxyReq.on('aborted', () => {
const cost = new Date() - start
const cost = Date.now() - start
const errorMsg = `代理请求被取消: ${url}, cost: ${cost} ms`
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
@ -209,7 +211,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
// 原始请求的事件监听
req.on('aborted', () => {
const cost = new Date() - start
const cost = Date.now() - start
const errorMsg = `请求被取消: ${url}, cost: ${cost} ms`
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
proxyReq.abort()
@ -219,12 +221,12 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
reject(new Error(errorMsg))
})
req.on('error', (e, req, res) => {
const cost = new Date() - start
const cost = Date.now() - start
log.error(`请求错误: ${url}, cost: ${cost} ms, error:`, e, ', rOptions:', jsonApi.stringify2(rOptions))
reject(e)
})
req.on('timeout', () => {
const cost = new Date() - start
const cost = Date.now() - start
const errorMsg = `请求超时: ${url}, cost: ${cost} ms`
log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions))
reject(new Error(errorMsg))

View File

@ -2,12 +2,35 @@ const defaultDns = require('node:dns')
const log = require('../../../utils/util.log.server')
const speedTest = require('../../speed')
function createIpChecker (tester) {
if (!tester || tester.backupList == null || tester.backupList.length === 0) {
return null
}
return (ip) => {
for (let i = 0; i < tester.backupList.length; i++) {
const item = tester.backupList[i]
if (item.host === ip) {
if (item.time > 0) {
return true // IP测速成功
}
if (item.status === 'failed') {
return false // IP测速失败
}
break
}
}
return true // IP测速未知
}
}
module.exports = {
createLookupFunc (res, dns, action, target, isDnsIntercept) {
createLookupFunc (res, dns, action, target, port, isDnsIntercept) {
target = target ? (`, target: ${target}`) : ''
return (hostname, options, callback) => {
const tester = speedTest.getSpeedTester(hostname)
const tester = speedTest.getSpeedTester(hostname, port)
if (tester) {
const aliveIpObj = tester.pickFastAliveIpObj()
if (aliveIpObj) {
@ -21,7 +44,10 @@ module.exports = {
log.info(`----- ${action}: ${hostname}, no alive ip${target}, tester: { "ready": ${tester.ready}, "backupList": ${JSON.stringify(tester.backupList)} }`)
}
}
dns.lookup(hostname).then((ip) => {
const ipChecker = createIpChecker(tester)
dns.lookup(hostname, ipChecker).then((ip) => {
if (isDnsIntercept) {
isDnsIntercept.dns = dns
isDnsIntercept.hostname = hostname
@ -29,35 +55,16 @@ module.exports = {
}
if (ip !== hostname) {
// 判断是否为测速失败的IP如果是则不使用当前IP
let isTestFailedIp = false
if (tester && tester.ready && tester.backupList && tester.backupList.length > 0) {
for (let i = 0; i < tester.backupList.length; i++) {
const item = tester.backupList[i]
if (item.host === ip) {
if (item.time == null) {
isTestFailedIp = true
}
break
}
}
}
if (isTestFailedIp === false) {
log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}${target} -----`)
if (res) {
res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`)
}
callback(null, ip, 4)
return
} else {
// 使用默认dns
log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}, skip test failed ip from dns '${dns.dnsName}: ${ip}'${target}, options:`, options)
log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}${target} -----`)
if (res) {
res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`)
}
callback(null, ip, 4)
} else {
// 使用默认dns
log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}${target}, options:`, options, ', dns:', dns)
log.info(`----- ${action}: ${hostname}, use default DNS: ${hostname}${target}, options:`, options, ', dns:', dns)
defaultDns.lookup(hostname, options, callback)
}
defaultDns.lookup(hostname, options, callback)
})
}
},

View File

@ -22,8 +22,8 @@ utils.createCA = function (CN) {
const keys = pki.rsa.generateKeyPair(2048)
const cert = pki.createCertificate()
cert.publicKey = keys.publicKey
cert.serialNumber = `${(new Date()).getTime()}`
cert.validity.notBefore = new Date(new Date() - (60 * 60 * 1000))
cert.serialNumber = `${Date.now()}`
cert.validity.notBefore = new Date(Date.now() - (60 * 60 * 1000))
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 20)
const attrs = [{
@ -87,7 +87,7 @@ utils.createFakeCertificateByDomain = function (caKey, caCert, domain, mappingHo
const cert = pki.createCertificate()
cert.publicKey = keys.publicKey
cert.serialNumber = `${(new Date()).getTime()}`
cert.serialNumber = `${Date.now()}`
cert.validity.notBefore = new Date()
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1)
cert.validity.notAfter = new Date()

View File

@ -1,31 +1,44 @@
// 1个小时不访问取消获取
// const { exec } = require('node:child_process')
const net = require('node:net')
const _ = require('lodash')
const log = require('../../utils/util.log.server')
const config = require('./config.js')
// const isWindows = process.platform === 'win32'
const DISABLE_TIMEOUT = 60 * 60 * 1000
class SpeedTester {
constructor ({ hostname }) {
constructor ({ hostname, port }) {
this.dnsMap = config.getConfig().dnsMap
this.hostname = hostname
this.lastReadTime = Date.now()
this.port = port || 443
this.ready = false
this.alive = []
this.backupList = []
this.keepCheckId = false
this.loadingIps = false
this.loadingTest = false
this.testCount = 0
this.test()
this.lastReadTime = Date.now()
this.keepCheckIntervalId = false
this.tryTestCount = 0
this.test() // 异步:初始化完成后先测速一次
}
pickFastAliveIpObj () {
this.touch()
if (this.alive.length === 0) {
this.test() // 异步
if (this.backupList.length > 0 && this.tryTestCount % 10 > 0) {
this.testBackups() // 异步
} else if (this.tryTestCount % 10 === 0) {
this.test() // 异步
}
this.tryTestCount++
return null
}
return this.alive[0]
@ -33,26 +46,27 @@ class SpeedTester {
touch () {
this.lastReadTime = Date.now()
if (!this.keepCheckId) {
if (!this.keepCheckIntervalId) {
this.startChecker()
}
}
startChecker () {
if (this.keepCheckId) {
clearInterval(this.keepCheckId)
if (this.keepCheckIntervalId) {
clearInterval(this.keepCheckIntervalId)
}
this.keepCheckId = setInterval(() => {
this.keepCheckIntervalId = setInterval(() => {
if (Date.now() - DISABLE_TIMEOUT > this.lastReadTime) {
// 超过很长时间没有访问,取消测试
clearInterval(this.keepCheckId)
clearInterval(this.keepCheckIntervalId)
this.keepCheckIntervalId = false
return
}
if (this.alive.length > 0) {
this.testBackups()
return
this.testBackups() // 异步
} else {
this.test() // 异步
}
this.test()
}, config.getConfig().interval)
}
@ -71,44 +85,56 @@ class SpeedTester {
promiseList.push(one)
}
await Promise.all(promiseList)
const items = []
for (const ip in ips) {
items.push({ host: ip, port: 443, dns: ips[ip].dns })
items.push({ host: ip, dns: ips[ip].dns })
}
return items
}
async getFromOneDns (dns) {
return await dns._lookupInternal(this.hostname)
return await dns._lookupWithPreSetIpList(this.hostname)
}
async test () {
const newList = await this.getIpListFromDns(this.dnsMap)
const newBackupList = [...newList, ...this.backupList]
this.backupList = _.unionBy(newBackupList, 'host')
this.testCount++
log.debug(`[speed] test start: ${this.hostname}, testCount: ${this.testCount}`)
log.info('[speed]', this.hostname, '➜ ip-list:', this.backupList)
await this.testBackups()
if (config.notify) {
config.notify({ key: 'test' })
try {
const newList = await this.getIpListFromDns(this.dnsMap)
const newBackupList = [...newList, ...this.backupList]
this.backupList = _.unionBy(newBackupList, 'host')
await this.testBackups()
log.info(`[speed] test end: ${this.hostname} ➜ ip-list:`, this.backupList, `, testCount: ${this.testCount}`)
if (config.notify) {
config.notify({ key: 'test' })
}
} catch (e) {
log.error(`[speed] test failed: ${this.hostname}, testCount: ${this.testCount}, error:`, e)
}
}
async testBackups () {
const testAll = []
const aliveList = []
for (const item of this.backupList) {
testAll.push(this.doTest(item, aliveList))
if (this.backupList.length > 0) {
const aliveList = []
const testAll = []
for (const item of this.backupList) {
testAll.push(this.doTest(item, aliveList))
}
await Promise.all(testAll)
this.alive = aliveList
}
await Promise.all(testAll)
this.alive = aliveList
this.ready = true
}
async doTest (item, aliveList) {
try {
const ret = await this.testOne(item)
item.title = `${ret.by}测速成功:${ret.target}`
log.info(`[speed] test success: ${this.hostname}${item.host}:${this.port} from DNS '${item.dns}'`)
_.merge(item, ret)
aliveList.push({ ...ret, ...item })
aliveList.sort((a, b) => a.time - b.time)
@ -125,48 +151,131 @@ class SpeedTester {
return a.time - b.time
})
} catch (e) {
if (e.message !== 'timeout') {
log.warn('[speed] test error: ', this.hostname, `${item.host}:${item.port} from DNS '${item.dns}'`, ', errorMsg:', e.message)
if (item.time == null) {
item.title = e.message
item.status = 'failed'
}
if (!e.message.includes('timeout')) {
log.warn(`[speed] test error: ${this.hostname}${item.host}:${this.port} from DNS '${item.dns}', errorMsg: ${e.message}`)
}
}
}
testOne (item) {
const timeout = 5000
const { host, port, dns } = item
const startTime = Date.now()
let isOver = false
testByTCP (item) {
return new Promise((resolve, reject) => {
const { host, dns } = item
const startTime = Date.now()
let isOver = false
const timeout = 5000
let timeoutId = null
const client = net.createConnection({ host, port }, () => {
// 'connect' 监听器
const connectionTime = Date.now()
const client = net.createConnection({ host, port: this.port }, () => {
isOver = true
clearTimeout(timeoutId)
resolve({ status: 'success', time: connectionTime - startTime })
const connectionTime = Date.now()
resolve({ status: 'success', by: 'TCP', target: `${host}:${this.port}`, time: connectionTime - startTime })
client.end()
})
client.on('end', () => {
})
client.on('error', (e) => {
if (e.message !== 'timeout') {
log.warn('[speed] test error: ', this.hostname, `${host}:${port} from DNS '${dns}', cost: ${Date.now() - startTime} ms, errorMsg:`, e.message)
}
isOver = true
clearTimeout(timeoutId)
log.warn('[speed] test by TCP error: ', this.hostname, `${host}:${this.port} from DNS '${dns}', cost: ${Date.now() - startTime} ms, errorMsg:`, e.message)
reject(e)
client.end()
})
timeoutId = setTimeout(() => {
if (isOver) {
return
}
log.warn('[speed] test timeout:', this.hostname, `${host}:${port} from DNS '${dns}', cost: ${Date.now() - startTime} ms`)
log.warn('[speed] test by TCP timeout:', this.hostname, `${host}:${this.port} from DNS '${dns}', cost: ${Date.now() - startTime} ms`)
reject(new Error('timeout'))
client.end()
}, timeout)
})
}
// 暂不使用
// testByPing (item) {
// return new Promise((resolve, reject) => {
// const { host, dns } = item
// const startTime = Date.now()
//
// // 设置超时程序
// let isOver = false
// const timeout = 5000
// const timeoutId = setTimeout(() => {
// if (!isOver) {
// log.warn('[speed] test by PING timeout:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms`)
// reject(new Error('timeout'))
// }
// }, timeout)
//
// // 协议选择如强制ping6
// const usePing6 = !isWindows && host.includes(':') // Windows无ping6命令
// const cmd = usePing6
// ? `ping6 -c 2 ${host}`
// : isWindows
// ? `ping -n 2 ${host}`
// : `ping -c 2 ${host}`
//
// log.debug('[speed] test by PING start:', this.hostname, `➜ ${host} from DNS '${dns}'`)
// exec(cmd, (error, stdout, _stderr) => {
// isOver = true
// clearTimeout(timeoutId)
//
// if (error) {
// log.warn('[speed] test by PING error:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms, error: 目标不可达或超时`)
// reject(new Error('目标不可达或超时'))
// return
// }
//
// // 提取延迟数据(正则匹配)
// const regex = /[=<](\d+(?:\.\d*)?)ms/gi // 适配Linux/Windows
// const times = []
// let match
// // eslint-disable-next-line no-cond-assign
// while ((match = regex.exec(stdout)) !== null) {
// times.push(Number.parseFloat(match[1]))
// }
//
// if (times.length === 0) {
// log.warn('[speed] test by PING error:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms, error: 无法解析延迟`)
// reject(new Error('无法解析延迟'))
// } else {
// // 计算平均延迟
// const avg = times.reduce((a, b) => a + b, 0) / times.length
// resolve({ status: 'success', by: 'PING', target: host, time: Math.round(avg) })
// }
// })
// })
// }
testOne (item) {
return new Promise((resolve, reject) => {
const thenFun = (ret) => {
resolve(ret)
}
// 先用TCP测速
this.testByTCP(item)
.then(thenFun)
.catch((e) => {
// // TCP测速失败再用 PING 测速
// this.testByPing(item)
// .then(thenFun)
// .catch((e2) => {
// reject(new Error(`TCP测速失败${e.message}PING测速失败${e2.message}`))
// })
reject(new Error(`TCP测速失败${item.host}:${this.port} ${e.message}`))
})
})
}
}
module.exports = SpeedTester

View File

@ -1,9 +1,9 @@
const config = {
notify () {},
dnsMap: {},
}
module.exports = {
getConfig () {
return config
},
notify: null,
}

View File

@ -6,6 +6,28 @@ const SpeedTester = require('./SpeedTester.js')
const SpeedTestPool = {
}
function addSpeedTest (hostname, port) {
if (!port) {
const idx = hostname.indexOf(':')
if (idx > 0 && idx === hostname.lastIndexOf(':')) {
const arr = hostname.split(':')
hostname = arr[0]
port = Number.parseInt(arr[1]) || 443
} else {
port = 443
}
}
// 443端口不拼接在key上
const key = port === 443 ? hostname : `${hostname}:${port}`
if (SpeedTestPool[key] == null) {
return SpeedTestPool[key] = new SpeedTester({ hostname, port })
}
return SpeedTestPool[key]
}
function initSpeedTest (runtimeConfig) {
const { enabled, hostnameList } = runtimeConfig
const conf = config.getConfig()
@ -14,45 +36,42 @@ function initSpeedTest (runtimeConfig) {
return
}
_.forEach(hostnameList, (hostname) => {
SpeedTestPool[hostname] = new SpeedTester({ hostname })
addSpeedTest(hostname)
})
log.info('[speed] enabled')
log.info('[speed] enabledSpeedTestPool:', SpeedTestPool)
}
function getAllSpeedTester () {
const allSpeed = {}
if (!config.getConfig().enabled) {
return allSpeed
if (config.getConfig().enabled) {
_.forEach(SpeedTestPool, (item, key) => {
allSpeed[key] = {
hostname: item.hostname,
port: item.port,
alive: item.alive,
backupList: item.backupList,
}
})
}
_.forEach(SpeedTestPool, (item, key) => {
allSpeed[key] = {
hostname: key,
alive: item.alive,
backupList: item.backupList,
}
})
return allSpeed
}
function getSpeedTester (hostname) {
function getSpeedTester (hostname, port) {
if (!config.getConfig().enabled) {
return
return null
}
let instance = SpeedTestPool[hostname]
if (instance == null) {
instance = new SpeedTester({ hostname })
SpeedTestPool[hostname] = instance
}
return instance
return addSpeedTest(hostname, port)
}
function registerNotify (notify) {
config.notify = notify
}
// function registerNotify (notify) {
// config.notify = notify
// }
function reSpeedTest () {
_.forEach(SpeedTestPool, (item, key) => {
item.test()
_.forEach(SpeedTestPool, (item, _key) => {
item.test() // 异步
})
}
@ -68,8 +87,8 @@ module.exports = {
SpeedTester,
initSpeedTest,
getSpeedTester,
getAllSpeedTester,
registerNotify,
// getAllSpeedTester,
// registerNotify,
reSpeedTest,
action,
}

View File

@ -1,7 +1,6 @@
const fs = require('node:fs')
const path = require('node:path')
const lodash = require('lodash')
const jsonApi = require('./json')
const dnsUtil = require('./lib/dns')
const interceptorImpls = require('./lib/interceptor')
const scriptInterceptor = require('./lib/interceptor/impl/res/script')
@ -110,7 +109,7 @@ module.exports = (serverConfig) => {
// 配置了白名单的域名,将跳过代理
const inWhiteList = !!matchUtil.matchHostname(whiteList, hostname, 'in whiteList')
if (inWhiteList) {
log.info(`为白名单域名,不拦截: ${hostname}, headers:`, jsonApi.stringify2(req.headers))
log.info(`为白名单域名,不拦截: ${hostname}`)
return false // 不拦截
}
@ -187,12 +186,12 @@ module.exports = (serverConfig) => {
if (impl.requestIntercept) {
// req拦截器
interceptor.requestIntercept = (context, req, res, ssl, next) => {
return impl.requestIntercept(context, interceptOpt, req, res, ssl, next, matched)
return impl.requestIntercept(context, interceptOpt, req, res, ssl, next, matched, interceptOpts.matched)
}
} else if (impl.responseIntercept) {
// res拦截器
interceptor.responseIntercept = (context, req, res, proxyReq, proxyRes, ssl, next) => {
return impl.responseIntercept(context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next, matched)
return impl.responseIntercept(context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next, matched, interceptOpts.matched)
}
}

View File

@ -1,5 +1,6 @@
const lodash = require('lodash')
const log = require('./util.log.server')
const mergeApi = require('@docmirror/dev-sidecar/src/merge')
function isMatched (url, regexp) {
if (regexp === '.*' || regexp === '*' || regexp === 'true' || regexp === true) {
@ -113,16 +114,6 @@ function merge (oldObj, newObj) {
}
})
}
function deleteNullItems (target) {
lodash.forEach(target, (item, key) => {
if (item == null || item === '[delete]') {
delete target[key]
}
if (lodash.isObject(item)) {
deleteNullItems(item)
}
})
}
function matchHostnameAll (hostMap, hostname, action) {
// log.debug('matchHostname-all:', action, hostMap)
@ -150,10 +141,29 @@ function matchHostnameAll (hostMap, hostname, action) {
// }
// 正则表达式匹配
if (hostname.match(regexp)) {
const matched = hostname.match(regexp)
if (matched) {
value = hostMap[regexp]
log.debug(`matchHostname-one: ${action}: '${hostname}' -> { "${regexp}": ${JSON.stringify(value)} }`)
values = merge(values, value)
// 设置matched
if (matched.length > 1) {
if (values.matched) {
// 合并array
matched.shift()
values.matched = [...values.matched, ...matched] // 拼接上多个matched
// 合并groups
if (matched.groups) {
values.matched.groups = merge(values.matched.groups, matched.groups)
} else {
values.matched.groups = matched.groups
}
} else {
values.matched = matched
}
}
}
}
@ -178,7 +188,7 @@ function matchHostnameAll (hostMap, hostname, action) {
}
if (!lodash.isEmpty(values)) {
deleteNullItems(values)
mergeApi.deleteNullItems(values)
log.info(`matchHostname-all: ${action}: '${hostname}':`, JSON.stringify(values))
return values
} else {

View File

@ -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++;
}
}

View File

@ -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++;
}
}

View File

@ -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')

View File

@ -9,17 +9,9 @@ const preSetIpList = matchUtil.domainMapRegexply({
]
})
// 常用DNS测试
const dnsProviders = dns.initDNS({
// https
cloudflare: {
type: 'https',
server: 'https://1.1.1.1/dns-query',
cacheSize: 1000,
},
quad9: {
server: 'https://9.9.9.9/dns-query',
cacheSize: 1000,
},
aliyun: {
type: 'https',
server: 'https://dns.alidns.com/dns-query',
@ -33,28 +25,10 @@ const dnsProviders = dns.initDNS({
safe360: {
server: 'https://doh.360.cn/dns-query',
cacheSize: 1000,
},
rubyfish: {
server: 'https://rubyfish.cn/dns-query',
cacheSize: 1000,
},
py233: {
server: ' https://i.233py.com/dns-query',
cacheSize: 1000,
forSNI: true,
},
// tls
cloudflareTLS: {
type: 'tls',
server: '1.1.1.1',
servername: 'cloudflare-dns.com',
cacheSize: 1000,
},
quad9TLS: {
server: 'tls://9.9.9.9',
servername: 'dns.quad9.net',
cacheSize: 1000,
},
aliyunTLS: {
server: 'tls://223.5.5.5:853',
cacheSize: 1000,
@ -93,7 +67,9 @@ const dnsProviders = dns.initDNS({
}, preSetIpList)
const presetHostname = 'xxx.com'
const hasPresetHostname = 'xxx.com'
const noPresetHostname = 'yyy.com'
const hostname1 = 'github.com'
const hostname2 = 'api.github.com'
const hostname3 = 'hk.docmirror.cn'
@ -104,26 +80,36 @@ const hostname6 = 'gh2.docmirror.top'
let ip
console.log('\n--------------- test ForSNI ---------------\n')
console.log(`===> test ForSNI: ${dnsProviders.ForSNI.dnsName}`, '\n\n')
assert.strictEqual(dnsProviders.ForSNI, dnsProviders.safe360)
const dnsProviders2 = dns.initDNS({
aliyun: {
server: 'udp://223.5.5.5',
},
}, {})
console.log(`===> test ForSNI2: ${dnsProviders2.ForSNI.dnsName}`, '\n\n')
assert.strictEqual(dnsProviders2.ForSNI, dnsProviders2.PreSet) // 未配置forSNI的DNS时默认使用PreSet作为ForSNI
console.log('\n--------------- test PreSet ---------------\n')
ip = await dnsProviders.PreSet.lookup(presetHostname)
console.log('===> test PreSet:', ip, '\n\n')
ip = await dnsProviders.PreSet.lookup(hasPresetHostname)
console.log(`===> test PreSet: ${hasPresetHostname} ->`, ip, '\n\n')
console.log('\n\n')
assert.strictEqual(ip, presetIp) // test preset
assert.strictEqual(ip, presetIp) // 预设过IP等于预设的IP
ip = await dnsProviders.PreSet.lookup(noPresetHostname)
console.log(`===> test PreSet: ${noPresetHostname} ->`, ip, '\n\n')
console.log('\n\n')
assert.strictEqual(ip, noPresetHostname) // 未预设IP等于域名自己
console.log('\n--------------- test https ---------------\n')
ip = await dnsProviders.cloudflare.lookup(presetHostname)
ip = await dnsProviders.aliyun.lookup(hasPresetHostname)
assert.strictEqual(ip, presetIp) // test preset
console.log('\n\n')
assert.strictEqual(dnsProviders.cloudflare.dnsType, 'HTTPS')
// ip = await dnsProviders.cloudflare.lookup(hostname1)
// console.log(`===> test cloudflare: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.quad9.dnsType, 'HTTPS')
// ip = await dnsProviders.quad9.lookup(hostname1)
// console.log(`===> test quad9: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.aliyun.dnsType, 'HTTPS')
ip = await dnsProviders.aliyun.lookup(hostname1)
console.log(`===> test aliyun: ${hostname1} ->`, ip, '\n\n')
@ -136,28 +122,12 @@ assert.strictEqual(dnsProviders.safe360.dnsType, 'HTTPS')
ip = await dnsProviders.safe360.lookup(hostname1)
console.log(`===> test safe360: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.rubyfish.dnsType, 'HTTPS')
// ip = await dnsProviders.rubyfish.lookup(hostname1)
// console.log(`===> test rubyfish: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.py233.dnsType, 'HTTPS')
// ip = await dnsProviders.py233.lookup(hostname1)
// console.log(`===> test py233: ${hostname1} ->`, ip, '\n\n')
console.log('\n--------------- test TLS ---------------\n')
ip = await dnsProviders.cloudflareTLS.lookup(presetHostname)
ip = await dnsProviders.aliyunTLS.lookup(hasPresetHostname)
assert.strictEqual(ip, presetIp) // test preset
console.log('\n\n')
assert.strictEqual(dnsProviders.cloudflareTLS.dnsType, 'TLS')
// ip = await dnsProviders.cloudflareTLS.lookup(hostname1)
// console.log(`===> test cloudflareTLS: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.quad9TLS.dnsType, 'TLS')
// ip = await dnsProviders.quad9TLS.lookup(hostname1)
// console.log(`===> test quad9TLS: ${hostname1} ->`, ip, '\n\n')
assert.strictEqual(dnsProviders.aliyunTLS.dnsType, 'TLS')
ip = await dnsProviders.aliyunTLS.lookup(hostname1)
console.log(`===> test aliyunTLS: ${hostname1} ->`, ip, '\n\n')
@ -172,7 +142,7 @@ console.log(`===> test safe360TLS: ${hostname1} ->`, ip, '\n\n')
console.log('\n--------------- test TCP ---------------\n')
ip = await dnsProviders.googleTCP.lookup(presetHostname)
ip = await dnsProviders.googleTCP.lookup(hasPresetHostname)
assert.strictEqual(ip, presetIp) // test preset
console.log('\n\n')
@ -186,7 +156,7 @@ console.log(`===> test aliyunTCP: ${hostname1} ->`, ip, '\n\n')
console.log('\n--------------- test UDP ---------------\n')
ip = await dnsProviders.googleUDP.lookup(presetHostname)
ip = await dnsProviders.googleUDP.lookup(hasPresetHostname)
assert.strictEqual(ip, presetIp) // test preset
console.log('\n\n')
@ -200,17 +170,13 @@ console.log(`===> test aliyunUDP: ${hostname1} ->`, ip, '\n\n')
dnsProviders.aliyunUDP.lookup(hostname1).then(ip0 => {
console.log(`===> test aliyunUDP: ${hostname1} ->`, ip0, '\n\n')
assert.strictEqual(ip0, ip)
})
dnsProviders.aliyunUDP.lookup(hostname2).then(ip0 => {
console.log(`===> test aliyunUDP: ${hostname2} ->`, ip0, '\n\n')
assert.notStrictEqual(ip0, ip)
})
dnsProviders.aliyunUDP.lookup('baidu.com').then(ip0 => {
console.log('===> test aliyunUDP: baidu.com ->', ip0, '\n\n')
assert.notStrictEqual(ip0, ip)
})
dnsProviders.aliyunUDP.lookup('gitee.com').then(ip0 => {
console.log('===> test aliyunUDP: gitee.com ->', ip0, '\n\n')
assert.notStrictEqual(ip0, ip)
})

View File

@ -26,3 +26,12 @@ assert.strictEqual(isEmpty(0), false)
assert.strictEqual(isEmpty(-1), false)
assert.strictEqual(isEmpty(''), false)
assert.strictEqual(isEmpty('1'), false)
// test lodash.unionBy
const list = [
{ host: 1, port: 1, dns: 2 },
{ host: 1, port: 1, dns: 3 },
{ host: 1, port: 2, dns: 3 },
{ host: 1, port: 2, dns: 3 },
]
console.info(lodash.unionBy(list, 'host', 'port'))

View File

@ -169,9 +169,6 @@ importers:
dns-over-http:
specifier: ^0.2.0
version: 0.2.0
dns-over-tls:
specifier: ^0.0.9
version: 0.0.9
is-browser:
specifier: ^2.1.0
version: 2.1.0
@ -3045,9 +3042,6 @@ packages:
dns-over-http@0.2.0:
resolution: {integrity: sha512-K+SyN2L3ljxJ2MFtOv/vRS+3/YEMLvOuH7MrmO5ejaubi4w02/DLqzoK1kBGKlQrT9ND57pbapeDf+ue8AElEA==}
dns-over-tls@0.0.9:
resolution: {integrity: sha512-IdI/Qgku2KQPLtUBsC6HDdK6bWIZADQMOYWtqJzRivV5Z+EDKIVAaC+tWE6lJ9vn11qj5L39ZaIaTj/14Lzgkw==}
dns-packet@4.2.0:
resolution: {integrity: sha512-bn1AKpfkFbm0MIioOMHZ5qJzl2uypdBwI4nYNsqvhjsegBhcKJUlCrMPWLx6JEezRjxZmxhtIz/FkBEur2l8Cw==}
engines: {node: '>=4'}
@ -10553,10 +10547,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
dns-over-tls@0.0.9:
dependencies:
dns-packet: 5.6.1
dns-packet@4.2.0:
dependencies:
ip: 1.1.9