feature: 1)新增 `UDP` 和 `TCP` 类型的DNS服务;2)修复 `TLS` 类型的DNS服务地址配置不生效的问题;3)删除 `ipaddress` 类型的DNS。
parent
982fa53d39
commit
7282098ec0
|
@ -1,8 +1,19 @@
|
||||||
const LRUCache = require('lru-cache')
|
const LRUCache = require('lru-cache')
|
||||||
const log = require('../../utils/util.log.server')
|
const log = require('../../utils/util.log.server')
|
||||||
|
const matchUtil = require('../../utils/util.match')
|
||||||
const { DynamicChoice } = require('../choice/index')
|
const { DynamicChoice } = require('../choice/index')
|
||||||
|
|
||||||
const cacheSize = 1024
|
function mapToList (ipMap) {
|
||||||
|
const ipList = []
|
||||||
|
for (const key in ipMap) {
|
||||||
|
if (ipMap[key]) { // 配置为 ture 时才生效
|
||||||
|
ipList.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ipList
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultCacheSize = 1024
|
||||||
|
|
||||||
class IpCache extends DynamicChoice {
|
class IpCache extends DynamicChoice {
|
||||||
constructor (hostname) {
|
constructor (hostname) {
|
||||||
|
@ -22,10 +33,12 @@ class IpCache extends DynamicChoice {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = class BaseDNS {
|
module.exports = class BaseDNS {
|
||||||
constructor (dnsName) {
|
constructor (dnsName, dnsType, cacheSize, preSetIpList) {
|
||||||
this.dnsName = dnsName
|
this.dnsName = dnsName
|
||||||
|
this.dnsType = dnsType
|
||||||
|
this.preSetIpList = preSetIpList
|
||||||
this.cache = new LRUCache({
|
this.cache = new LRUCache({
|
||||||
maxSize: cacheSize,
|
maxSize: (cacheSize > 0 ? cacheSize : defaultCacheSize),
|
||||||
sizeCalculation: () => {
|
sizeCalculation: () => {
|
||||||
return 1
|
return 1
|
||||||
},
|
},
|
||||||
|
@ -53,7 +66,7 @@ module.exports = class BaseDNS {
|
||||||
}
|
}
|
||||||
|
|
||||||
const t = new Date()
|
const t = new Date()
|
||||||
let ipList = await this._lookup(hostname)
|
let ipList = await this._lookupInternal(hostname)
|
||||||
if (ipList == null) {
|
if (ipList == null) {
|
||||||
// 没有获取到ipv4地址
|
// 没有获取到ipv4地址
|
||||||
ipList = []
|
ipList = []
|
||||||
|
@ -61,12 +74,55 @@ module.exports = class BaseDNS {
|
||||||
ipList.push(hostname) // 把原域名加入到统计里去
|
ipList.push(hostname) // 把原域名加入到统计里去
|
||||||
|
|
||||||
ipCache.setBackupList(ipList)
|
ipCache.setBackupList(ipList)
|
||||||
log.info(`[DNS '${this.dnsName}']: ${hostname} ➜ ${ipCache.value} (${new Date() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache))
|
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
|
return ipCache.value
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`[DNS '${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) {
|
||||||
|
// 获取当前域名的预设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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this._lookup(hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
async _lookup (hostname) {
|
||||||
|
const start = Date.now()
|
||||||
|
try {
|
||||||
|
const response = await this._doDnsQuery(hostname)
|
||||||
|
const cost = Date.now() - start
|
||||||
|
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`)
|
||||||
|
} else {
|
||||||
|
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IPv4地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
} catch (e) {
|
||||||
|
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS query error, hostname: ${hostname}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}, cost: ${Date.now() - start} ms, error:`, e)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,64 +1,16 @@
|
||||||
const { promisify } = require('node:util')
|
const { promisify } = require('node:util')
|
||||||
const doh = require('dns-over-http')
|
const doh = require('dns-over-http')
|
||||||
const log = require('../../utils/util.log.server')
|
|
||||||
const matchUtil = require('../../utils/util.match')
|
|
||||||
const BaseDNS = require('./base')
|
const BaseDNS = require('./base')
|
||||||
|
|
||||||
const dohQueryAsync = promisify(doh.query)
|
const dohQueryAsync = promisify(doh.query)
|
||||||
|
|
||||||
function mapToList (ipMap) {
|
|
||||||
const ipList = []
|
|
||||||
for (const key in ipMap) {
|
|
||||||
if (ipMap[key]) { // 配置为 ture 时才生效
|
|
||||||
ipList.push(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ipList
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = class DNSOverHTTPS extends BaseDNS {
|
module.exports = class DNSOverHTTPS extends BaseDNS {
|
||||||
constructor (dnsName, dnsServer, preSetIpList) {
|
constructor (dnsName, cacheSize, preSetIpList, dnsServer) {
|
||||||
super(dnsName)
|
super(dnsName, 'HTTPS', cacheSize, preSetIpList)
|
||||||
this.dnsServer = dnsServer
|
this.dnsServer = dnsServer
|
||||||
this.preSetIpList = preSetIpList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _lookup (hostname) {
|
async _doDnsQuery (hostname) {
|
||||||
// 获取当前域名的预设IP列表
|
return await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }])
|
||||||
let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, 'matched preSetIpList')
|
|
||||||
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
|
|
||||||
return hostnamePreSetIpList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 未预设当前域名的IP列表,则从dns服务器获取
|
|
||||||
const start = new Date()
|
|
||||||
try {
|
|
||||||
const result = await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }])
|
|
||||||
const cost = new Date() - start
|
|
||||||
if (result.answers.length === 0) {
|
|
||||||
// 说明没有获取到ip
|
|
||||||
log.info(`DNS '${this.dnsName}' 没有该域名的IP地址: ${hostname}, cost: ${cost} ms`)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
const ret = result.answers.filter(item => item.type === 'A').map(item => item.data)
|
|
||||||
if (ret.length === 0) {
|
|
||||||
log.info(`DNS '${this.dnsName}' 没有该域名的IPv4地址: ${hostname}, cost: ${cost} ms`)
|
|
||||||
} else {
|
|
||||||
log.info(`DNS '${this.dnsName}' 获取到该域名的IPv4地址: ${hostname} ${JSON.stringify(ret)}, cost: ${cost} ms`)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
} catch (e) {
|
|
||||||
log.warn(`DNS query error: ${hostname}, dns: ${this.dnsName}, dnsServer: ${this.dnsServer}, cost: ${new Date() - start} ms, error:`, e)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
const matchUtil = require('../../utils/util.match')
|
const matchUtil = require('../../utils/util.match')
|
||||||
const DNSOverHTTPS = require('./https.js')
|
|
||||||
const DNSOverIpAddress = require('./ipaddress.js')
|
|
||||||
const DNSOverPreSetIpList = require('./preset.js')
|
const DNSOverPreSetIpList = require('./preset.js')
|
||||||
|
const DNSOverHTTPS = require('./https.js')
|
||||||
const DNSOverTLS = require('./tls.js')
|
const DNSOverTLS = require('./tls.js')
|
||||||
|
const DNSOverTCP = require('./tcp.js')
|
||||||
|
const DNSOverUDP = require('./udp.js')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
initDNS (dnsProviders, preSetIpList) {
|
initDNS (dnsProviders, preSetIpList) {
|
||||||
|
@ -12,17 +13,65 @@ module.exports = {
|
||||||
for (const provider in dnsProviders) {
|
for (const provider in dnsProviders) {
|
||||||
const conf = dnsProviders[provider]
|
const conf = dnsProviders[provider]
|
||||||
|
|
||||||
if (conf.type === 'ipaddress') {
|
// 获取DNS服务器
|
||||||
dnsMap[provider] = new DNSOverIpAddress(provider)
|
let server = conf.server || conf.host
|
||||||
} else if (conf.type === 'https') {
|
if (server != null) {
|
||||||
dnsMap[provider] = new DNSOverHTTPS(provider, conf.server, preSetIpList)
|
server = server.replace(/\s+/, '')
|
||||||
} else {
|
}
|
||||||
dnsMap[provider] = new DNSOverTLS(provider)
|
if (!server) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置DNS名称到name属性中
|
// 获取DNS类型
|
||||||
dnsMap[provider].name = provider
|
let type = conf.type
|
||||||
dnsMap[provider].type = conf.type
|
if (type == null) {
|
||||||
|
if (server.startsWith('https://') || server.startsWith('http://')) {
|
||||||
|
type = 'https'
|
||||||
|
} else if (server.startsWith('tls://')) {
|
||||||
|
type = 'tls'
|
||||||
|
} else if (server.startsWith('tcp://')) {
|
||||||
|
type = 'tcp'
|
||||||
|
} else if (server.includes('://') && !server.startsWith('udp://')) {
|
||||||
|
throw new Error(`Unknown type DNS: ${server}, provider: ${provider}`)
|
||||||
|
} else {
|
||||||
|
type = 'udp'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
type = type.replace(/\s+/, '').toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建DNS对象
|
||||||
|
if (type === 'https' || type === 'doh' || type === 'dns-over-https') {
|
||||||
|
if (!server.includes('/')) {
|
||||||
|
server = `https://${server}/dns-query`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基于 https
|
||||||
|
dnsMap[provider] = new DNSOverHTTPS(provider, conf.cacheSize, preSetIpList, server)
|
||||||
|
} else {
|
||||||
|
// 获取DNS端口
|
||||||
|
let port = conf.port
|
||||||
|
|
||||||
|
// 处理带协议的DNS服务地址
|
||||||
|
if (server.includes('://')) {
|
||||||
|
server = server.split('://')[1]
|
||||||
|
}
|
||||||
|
// 处理带端口的DNS服务地址
|
||||||
|
if (port == null && server.includes(':')) {
|
||||||
|
[server, port] = server.split(':')
|
||||||
|
}
|
||||||
|
|
||||||
|
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') {
|
||||||
|
// 基于 tcp
|
||||||
|
dnsMap[provider] = new DNSOverTCP(provider, conf.cacheSize, preSetIpList, server, port)
|
||||||
|
} else {
|
||||||
|
// 基于 udp
|
||||||
|
dnsMap[provider] = new DNSOverUDP(provider, conf.cacheSize, preSetIpList, server, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建预设IP的DNS
|
// 创建预设IP的DNS
|
||||||
|
@ -31,16 +80,14 @@ module.exports = {
|
||||||
return dnsMap
|
return dnsMap
|
||||||
},
|
},
|
||||||
hasDnsLookup (dnsConfig, hostname) {
|
hasDnsLookup (dnsConfig, hostname) {
|
||||||
let providerName = null
|
|
||||||
|
|
||||||
// 先匹配 预设IP配置
|
// 先匹配 预设IP配置
|
||||||
const hostnamePreSetIpList = matchUtil.matchHostname(dnsConfig.preSetIpList, hostname, 'matched preSetIpList')
|
const hostnamePreSetIpList = matchUtil.matchHostname(dnsConfig.preSetIpList, hostname, 'matched preSetIpList(hasDnsLookup)')
|
||||||
if (hostnamePreSetIpList) {
|
if (hostnamePreSetIpList) {
|
||||||
return dnsConfig.dnsMap.PreSet
|
return dnsConfig.dnsMap.PreSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// 再匹配 DNS映射配置
|
// 再匹配 DNS映射配置
|
||||||
providerName = matchUtil.matchHostname(dnsConfig.mapping, hostname, 'get dns providerName')
|
const providerName = matchUtil.matchHostname(dnsConfig.mapping, hostname, 'get dns providerName')
|
||||||
|
|
||||||
// 由于DNS中的usa已重命名为cloudflare,所以做以下处理,为了向下兼容
|
// 由于DNS中的usa已重命名为cloudflare,所以做以下处理,为了向下兼容
|
||||||
if (providerName === 'usa' && dnsConfig.dnsMap.usa == null && dnsConfig.dnsMap.cloudflare != null) {
|
if (providerName === 'usa' && dnsConfig.dnsMap.usa == null && dnsConfig.dnsMap.cloudflare != null) {
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
const axios = require('axios')
|
|
||||||
const log = require('../../utils/util.log.server')
|
|
||||||
const BaseDNS = require('./base')
|
|
||||||
|
|
||||||
module.exports = class DNSOverIpAddress extends BaseDNS {
|
|
||||||
async _lookup (hostname) {
|
|
||||||
const url = `https://${hostname}.ipaddress.com`
|
|
||||||
|
|
||||||
// const res = fs.readFileSync(path.resolve(__dirname, './data.txt')).toString()
|
|
||||||
const res = await axios.get(url)
|
|
||||||
if (res.status !== 200 && res.status !== 201) {
|
|
||||||
log.error(`[dns] get ${hostname} ipaddress: error: ${res}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const ret = res.data
|
|
||||||
|
|
||||||
const regexp = /<tr><th>IP Address<\/th><td><ul class="comma-separated"><li>([^<]*)<\/li><\/ul><\/td><\/tr>/g
|
|
||||||
const matched = regexp.exec(ret)
|
|
||||||
let ip = null
|
|
||||||
|
|
||||||
if (matched && matched.length >= 1) {
|
|
||||||
ip = matched[1]
|
|
||||||
log.info(`[dns] get ${hostname} ipaddress:${ip}`)
|
|
||||||
return [ip]
|
|
||||||
}
|
|
||||||
log.warn(`[dns] get ${hostname} ipaddress: error`)
|
|
||||||
return null
|
|
||||||
|
|
||||||
// const { answers } = await dnstls.query(hostname)
|
|
||||||
//
|
|
||||||
// const answer = answers.find(answer => answer.type === 'A' && answer.class === 'IN')
|
|
||||||
//
|
|
||||||
// log.info('dns lookup:', hostname, answer)
|
|
||||||
// if (answer) {
|
|
||||||
// return answer.data
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
lookup () {
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,40 +1,11 @@
|
||||||
const matchUtil = require('../../utils/util.match')
|
|
||||||
const BaseDNS = require('./base')
|
const BaseDNS = require('./base')
|
||||||
|
|
||||||
function mapToList (ipMap) {
|
|
||||||
const ipList = []
|
|
||||||
for (const key in ipMap) {
|
|
||||||
if (ipMap[key]) { // 配置为 ture 时才生效
|
|
||||||
ipList.push(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ipList
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = class DNSOverPreSetIpList extends BaseDNS {
|
module.exports = class DNSOverPreSetIpList extends BaseDNS {
|
||||||
constructor (preSetIpList) {
|
constructor (preSetIpList) {
|
||||||
super()
|
super('PreSet', 'PreSet', null, preSetIpList)
|
||||||
this.preSetIpList = preSetIpList
|
|
||||||
this.name = 'PreSet'
|
|
||||||
this.type = 'PreSet'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _lookup (hostname) {
|
async _lookup (_hostname) {
|
||||||
// 获取当前域名的预设IP列表
|
|
||||||
let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, 'matched preSetIpList')
|
|
||||||
if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) {
|
|
||||||
if (hostnamePreSetIpList.length > 0) {
|
|
||||||
hostnamePreSetIpList = hostnamePreSetIpList.slice()
|
|
||||||
} else {
|
|
||||||
hostnamePreSetIpList = mapToList(hostnamePreSetIpList)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hostnamePreSetIpList.length > 0) {
|
|
||||||
return hostnamePreSetIpList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 未预设当前域名的IP列表
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
const net = require('node:net')
|
||||||
|
const { Buffer } = require('node:buffer')
|
||||||
|
const dnsPacket = require('dns-packet')
|
||||||
|
const randi = require('random-int')
|
||||||
|
const BaseDNS = require('./base')
|
||||||
|
|
||||||
|
const defaultPort = 53 // UDP类型的DNS服务默认端口号
|
||||||
|
|
||||||
|
module.exports = class DNSOverTCP extends BaseDNS {
|
||||||
|
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) {
|
||||||
|
super(dnsName, 'TCP', cacheSize, preSetIpList)
|
||||||
|
this.dnsServer = dnsServer
|
||||||
|
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
|
||||||
|
}
|
||||||
|
|
||||||
|
_doDnsQuery (hostname) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 构造 DNS 查询报文
|
||||||
|
const packet = dnsPacket.encode({
|
||||||
|
flags: dnsPacket.RECURSION_DESIRED,
|
||||||
|
type: 'query',
|
||||||
|
id: randi(0x0, 0xFFFF),
|
||||||
|
questions: [{
|
||||||
|
type: 'A',
|
||||||
|
name: hostname,
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
|
||||||
|
// --- TCP 查询 ---
|
||||||
|
const tcpClient = net.createConnection({
|
||||||
|
host: this.dnsServer,
|
||||||
|
port: this.dnsServerPort,
|
||||||
|
}, () => {
|
||||||
|
// TCP DNS 报文前需添加 2 字节长度头
|
||||||
|
const lengthBuffer = Buffer.alloc(2)
|
||||||
|
lengthBuffer.writeUInt16BE(packet.length)
|
||||||
|
tcpClient.write(Buffer.concat([lengthBuffer, packet]))
|
||||||
|
})
|
||||||
|
|
||||||
|
tcpClient.on('data', (data) => {
|
||||||
|
const length = data.readUInt16BE(0)
|
||||||
|
const response = dnsPacket.decode(data.subarray(2, 2 + length))
|
||||||
|
resolve(response)
|
||||||
|
})
|
||||||
|
|
||||||
|
tcpClient.on('error', (err) => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,27 @@
|
||||||
const dnstls = require('dns-over-tls')
|
const dnstls = require('dns-over-tls')
|
||||||
const log = require('../../utils/util.log.server')
|
|
||||||
const BaseDNS = require('./base')
|
const BaseDNS = require('./base')
|
||||||
|
|
||||||
|
const defaultPort = 853
|
||||||
|
|
||||||
module.exports = class DNSOverTLS extends BaseDNS {
|
module.exports = class DNSOverTLS extends BaseDNS {
|
||||||
async _lookup (hostname) {
|
constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort, dnsServerName) {
|
||||||
const { answers } = await dnstls.query(hostname)
|
super(dnsName, 'TLS', cacheSize, preSetIpList)
|
||||||
|
this.dnsServer = dnsServer
|
||||||
|
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
|
||||||
|
this.dnsServerName = dnsServerName
|
||||||
|
}
|
||||||
|
|
||||||
const answer = answers.find(answer => answer.type === 'A' && answer.class === 'IN')
|
async _doDnsQuery (hostname) {
|
||||||
|
const options = {
|
||||||
|
host: this.dnsServer,
|
||||||
|
port: this.dnsServerPort,
|
||||||
|
servername: this.dnsServerName || this.dnsServer,
|
||||||
|
|
||||||
log.info('DNS lookup:', hostname, answer)
|
name: hostname,
|
||||||
if (answer) {
|
klass: 'IN',
|
||||||
return answer.data
|
type: 'A',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return await dnstls.query(options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
const dgram = require('node:dgram')
|
||||||
|
const dnsPacket = require('dns-packet')
|
||||||
|
const randi = require('random-int')
|
||||||
|
const BaseDNS = require('./base')
|
||||||
|
|
||||||
|
const udpClient = dgram.createSocket('udp4')
|
||||||
|
|
||||||
|
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.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
|
||||||
|
}
|
||||||
|
|
||||||
|
_doDnsQuery (hostname) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 构造 DNS 查询报文
|
||||||
|
const packet = dnsPacket.encode({
|
||||||
|
flags: dnsPacket.RECURSION_DESIRED,
|
||||||
|
type: 'query',
|
||||||
|
id: randi(0x0, 0xFFFF),
|
||||||
|
questions: [{
|
||||||
|
type: 'A',
|
||||||
|
name: hostname,
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
|
||||||
|
// 发送 UDP 查询
|
||||||
|
udpClient.send(packet, 0, packet.length, this.dnsServerPort, this.dnsServer, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 接收 UDP 响应
|
||||||
|
udpClient.on('message', (msg) => {
|
||||||
|
const response = dnsPacket.decode(msg)
|
||||||
|
resolve(response)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -143,7 +143,7 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
|
||||||
if (isDnsIntercept && isDnsIntercept.dns && isDnsIntercept.ip !== isDnsIntercept.hostname) {
|
if (isDnsIntercept && isDnsIntercept.dns && isDnsIntercept.ip !== isDnsIntercept.hostname) {
|
||||||
const { dns, ip, hostname } = isDnsIntercept
|
const { dns, ip, hostname } = isDnsIntercept
|
||||||
dns.count(hostname, ip, true)
|
dns.count(hostname, ip, true)
|
||||||
log.error(`记录ip失败次数,用于优选ip! hostname: ${hostname}, ip: ${ip}, reason: ${errorMsg}, dns: ${dns.name}`)
|
log.error(`记录ip失败次数,用于优选ip! hostname: ${hostname}, ip: ${ip}, reason: ${errorMsg}, dns: ${dns.dnsName}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
proxySocket.on('error', (e) => {
|
proxySocket.on('error', (e) => {
|
||||||
|
@ -157,7 +157,7 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
|
||||||
if (isDnsIntercept && isDnsIntercept.dns && isDnsIntercept.ip !== isDnsIntercept.hostname) {
|
if (isDnsIntercept && isDnsIntercept.dns && isDnsIntercept.ip !== isDnsIntercept.hostname) {
|
||||||
const { dns, ip, hostname } = isDnsIntercept
|
const { dns, ip, hostname } = isDnsIntercept
|
||||||
dns.count(hostname, ip, true)
|
dns.count(hostname, ip, true)
|
||||||
log.error(`记录ip失败次数,用于优选ip! hostname: ${hostname}, ip: ${ip}, reason: ${errorMsg}, dns: ${dns.name}`)
|
log.error(`记录ip失败次数,用于优选ip! hostname: ${hostname}, ip: ${ip}, reason: ${errorMsg}, dns: ${dns.dnsName}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
|
||||||
if (isDnsIntercept && isDnsIntercept.dns && isDnsIntercept.ip !== isDnsIntercept.hostname) {
|
if (isDnsIntercept && isDnsIntercept.dns && isDnsIntercept.ip !== isDnsIntercept.hostname) {
|
||||||
const { dns, ip, hostname } = isDnsIntercept
|
const { dns, ip, hostname } = isDnsIntercept
|
||||||
dns.count(hostname, ip, true)
|
dns.count(hostname, ip, true)
|
||||||
log.error(`记录ip失败次数,用于优选ip! hostname: ${hostname}, ip: ${ip}, reason: ${reason}, dns: ${dns.name}`)
|
log.error(`记录ip失败次数,用于优选ip! hostname: ${hostname}, ip: ${ip}, reason: ${reason}, dns: ${dns.dnsName}`)
|
||||||
}
|
}
|
||||||
const counter = context.requestCount
|
const counter = context.requestCount
|
||||||
if (counter != null) {
|
if (counter != null) {
|
||||||
|
@ -123,8 +123,8 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
|
||||||
}
|
}
|
||||||
if (dns) {
|
if (dns) {
|
||||||
rOptions.lookup = dnsLookup.createLookupFunc(res, dns, 'request url', url, isDnsIntercept)
|
rOptions.lookup = dnsLookup.createLookupFunc(res, dns, 'request url', url, isDnsIntercept)
|
||||||
log.debug(`域名 ${rOptions.hostname} DNS: ${dns.name}`)
|
log.debug(`域名 ${rOptions.hostname} DNS: ${dns.dnsName}`)
|
||||||
res.setHeader('DS-DNS', dns.name)
|
res.setHeader('DS-DNS', dns.dnsName)
|
||||||
} else {
|
} else {
|
||||||
log.info(`域名 ${rOptions.hostname} 在DNS中未配置`)
|
log.info(`域名 ${rOptions.hostname} 在DNS中未配置`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,15 +43,15 @@ module.exports = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isTestFailedIp === false) {
|
if (isTestFailedIp === false) {
|
||||||
log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.name}': ${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.name === '预设IP' ? 'PreSet' : dns.name}`)
|
res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`)
|
||||||
}
|
}
|
||||||
callback(null, ip, 4)
|
callback(null, ip, 4)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
// 使用默认dns
|
// 使用默认dns
|
||||||
log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}, skip test failed ip from dns '${dns.name}: ${ip}'${target}, options:`, options)
|
log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}, skip test failed ip from dns '${dns.dnsName}: ${ip}'${target}, options:`, options)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 使用默认dns
|
// 使用默认dns
|
||||||
|
|
|
@ -79,7 +79,7 @@ class SpeedTester {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFromOneDns (dns) {
|
async getFromOneDns (dns) {
|
||||||
return await dns._lookup(this.hostname)
|
return await dns._lookupInternal(this.hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
async test () {
|
async test () {
|
||||||
|
|
|
@ -3,11 +3,6 @@ const SpeedTest = require('../src/lib/speed/index.js')
|
||||||
const SpeedTester = require('../src/lib/speed/SpeedTester.js')
|
const SpeedTester = require('../src/lib/speed/SpeedTester.js')
|
||||||
|
|
||||||
const dnsMap = dns.initDNS({
|
const dnsMap = dns.initDNS({
|
||||||
// ipaddress: {
|
|
||||||
// type: 'ipaddress',
|
|
||||||
// server: 'ipaddress',
|
|
||||||
// cacheSize: 1000
|
|
||||||
// },
|
|
||||||
cloudflare: {
|
cloudflare: {
|
||||||
type: 'https',
|
type: 'https',
|
||||||
server: 'https://1.1.1.1/dns-query',
|
server: 'https://1.1.1.1/dns-query',
|
||||||
|
|
|
@ -1,56 +1,99 @@
|
||||||
|
import assert from 'node:assert'
|
||||||
import dns from '../src/lib/dns/index.js'
|
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
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
const dnsProviders = dns.initDNS({
|
const dnsProviders = dns.initDNS({
|
||||||
aliyun: {
|
// https
|
||||||
type: 'https',
|
|
||||||
server: 'https://dns.alidns.com/dns-query',
|
|
||||||
cacheSize: 1000,
|
|
||||||
},
|
|
||||||
cloudflare: {
|
cloudflare: {
|
||||||
type: 'https',
|
type: 'https',
|
||||||
server: 'https://1.1.1.1/dns-query',
|
server: 'https://1.1.1.1/dns-query',
|
||||||
cacheSize: 1000,
|
cacheSize: 1000,
|
||||||
},
|
},
|
||||||
ipaddress: {
|
|
||||||
type: 'ipaddress',
|
|
||||||
server: 'ipaddress',
|
|
||||||
cacheSize: 1000,
|
|
||||||
},
|
|
||||||
quad9: {
|
quad9: {
|
||||||
type: 'https',
|
|
||||||
server: 'https://9.9.9.9/dns-query',
|
server: 'https://9.9.9.9/dns-query',
|
||||||
cacheSize: 1000,
|
cacheSize: 1000,
|
||||||
},
|
},
|
||||||
rubyfish: {
|
aliyun: {
|
||||||
type: 'https',
|
type: 'https',
|
||||||
|
server: 'https://dns.alidns.com/dns-query',
|
||||||
|
cacheSize: 1000,
|
||||||
|
},
|
||||||
|
aliyun2: {
|
||||||
|
type: 'https',
|
||||||
|
server: 'dns.alidns.com', // 会自动补上 `https://` 和 `/dns-query`
|
||||||
|
cacheSize: 1000,
|
||||||
|
},
|
||||||
|
safe360: {
|
||||||
|
server: 'https://doh.360.cn/dns-query',
|
||||||
|
cacheSize: 1000,
|
||||||
|
},
|
||||||
|
rubyfish: {
|
||||||
server: 'https://rubyfish.cn/dns-query',
|
server: 'https://rubyfish.cn/dns-query',
|
||||||
cacheSize: 1000,
|
cacheSize: 1000,
|
||||||
},
|
},
|
||||||
py233: {
|
py233: {
|
||||||
type: 'https',
|
|
||||||
server: ' https://i.233py.com/dns-query',
|
server: ' https://i.233py.com/dns-query',
|
||||||
cacheSize: 1000,
|
cacheSize: 1000,
|
||||||
},
|
},
|
||||||
|
|
||||||
// sb: {
|
// tls
|
||||||
// type: 'https',
|
cloudflareTLS: {
|
||||||
// server: 'https://doh.dns.sb/dns-query',
|
type: 'tls',
|
||||||
// cacheSize: 1000
|
server: '1.1.1.1',
|
||||||
// },
|
servername: 'cloudflare-dns.com',
|
||||||
// adguard: {
|
cacheSize: 1000,
|
||||||
// type: 'https',
|
},
|
||||||
// server: ' https://dns.adguard.com/dns-query',
|
quad9TLS: {
|
||||||
// cacheSize: 1000
|
server: 'tls://9.9.9.9',
|
||||||
// }
|
servername: 'dns.quad9.net',
|
||||||
})
|
cacheSize: 1000,
|
||||||
|
},
|
||||||
|
aliyunTLS: {
|
||||||
|
server: 'tls://223.5.5.5:853',
|
||||||
|
cacheSize: 1000,
|
||||||
|
},
|
||||||
|
aliyunTLS2: {
|
||||||
|
server: 'tls://223.6.6.6',
|
||||||
|
cacheSize: 1000,
|
||||||
|
},
|
||||||
|
safe360TLS: {
|
||||||
|
server: 'tls://dot.360.cn',
|
||||||
|
cacheSize: 1000,
|
||||||
|
},
|
||||||
|
|
||||||
// const test = '111<tr><th>IP Address</th><td><ul class="comma-separated"><li>140.82.113.4</li></ul></td></tr>2222'
|
// tcp
|
||||||
// // <tr><th>IP Address</th><td><ul class="comma-separated"><li>140.82.113.4</li></ul></td></tr>
|
googleTCP: {
|
||||||
// // <tr><th>IP Address</th><td><ul class="comma-separated"><li>(.*)</li></ul></td></tr>
|
type: 'tcp',
|
||||||
// const regexp = /<tr><th>IP Address<\/th><td><ul class="comma-separated"><li>(.*)<\/li><\/ul><\/td><\/tr>/
|
server: '8.8.8.8',
|
||||||
// const matched = regexp.exec(test)
|
port: 53,
|
||||||
// console.log('data:', matched)
|
cacheSize: 1000,
|
||||||
|
},
|
||||||
|
aliyunTCP: {
|
||||||
|
server: 'tcp://223.5.5.5',
|
||||||
|
cacheSize: 1000,
|
||||||
|
},
|
||||||
|
|
||||||
|
// udp
|
||||||
|
googleUDP: {
|
||||||
|
// type: 'udp', // 默认是udp可以不用标
|
||||||
|
server: '8.8.8.8',
|
||||||
|
cacheSize: 1000,
|
||||||
|
},
|
||||||
|
aliyunUDP: {
|
||||||
|
server: 'udp://223.5.5.5',
|
||||||
|
cacheSize: 1000,
|
||||||
|
},
|
||||||
|
}, preSetIpList)
|
||||||
|
|
||||||
|
|
||||||
|
const presetHostname = 'xxx.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'
|
||||||
|
@ -61,24 +104,96 @@ const hostname6 = 'gh2.docmirror.top'
|
||||||
let ip
|
let ip
|
||||||
|
|
||||||
|
|
||||||
// console.log('test cloudflare')
|
console.log('\n--------------- test PreSet ---------------\n')
|
||||||
|
ip = await dnsProviders.PreSet.lookup(presetHostname)
|
||||||
|
assert.strictEqual(ip, presetIp) // test preset
|
||||||
|
console.log('===> test PreSet:', ip, '\n\n')
|
||||||
|
console.log('\n\n')
|
||||||
|
|
||||||
|
|
||||||
|
console.log('\n--------------- test https ---------------\n')
|
||||||
|
ip = await dnsProviders.cloudflare.lookup(presetHostname)
|
||||||
|
assert.strictEqual(ip, presetIp) // test preset
|
||||||
|
console.log('\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.cloudflare.dnsType, 'HTTPS')
|
||||||
// ip = await dnsProviders.cloudflare.lookup(hostname1)
|
// ip = await dnsProviders.cloudflare.lookup(hostname1)
|
||||||
// console.log('ip:', ip)
|
// console.log('===> test cloudflare:', ip, '\n\n')
|
||||||
// ip = await dnsProviders.cloudflare.lookup(hostname2)
|
|
||||||
// console.log('ip:', ip)
|
|
||||||
// ip = await dnsProviders.cloudflare.lookup(hostname3)
|
|
||||||
// console.log('ip:', ip)
|
|
||||||
// ip = await dnsProviders.cloudflare.lookup(hostname4)
|
|
||||||
// console.log('ip:', ip)
|
|
||||||
// ip = await dnsProviders.cloudflare.lookup(hostname5)
|
|
||||||
// console.log('ip:', ip)
|
|
||||||
// ip = await dnsProviders.cloudflare.lookup(hostname6)
|
|
||||||
// console.log('ip:', ip)
|
|
||||||
|
|
||||||
// console.log('test py233')
|
assert.strictEqual(dnsProviders.quad9.dnsType, 'HTTPS')
|
||||||
|
// ip = await dnsProviders.quad9.lookup(hostname1)
|
||||||
|
// console.log('===> test quad9:', ip, '\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.aliyun.dnsType, 'HTTPS')
|
||||||
|
// ip = await dnsProviders.aliyun.lookup(hostname1)
|
||||||
|
// console.log('===> test aliyun:', ip, '\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.aliyun2.dnsType, 'HTTPS')
|
||||||
|
// ip = await dnsProviders.aliyun2.lookup(hostname1)
|
||||||
|
// console.log('===> test aliyun2:', ip, '\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.safe360.dnsType, 'HTTPS')
|
||||||
|
// ip = await dnsProviders.safe360.lookup(hostname1)
|
||||||
|
// console.log('===> test safe360:', ip, '\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.rubyfish.dnsType, 'HTTPS')
|
||||||
|
// ip = await dnsProviders.rubyfish.lookup(hostname1)
|
||||||
|
// console.log('===> test rubyfish:', ip, '\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.py233.dnsType, 'HTTPS')
|
||||||
// ip = await dnsProviders.py233.lookup(hostname1)
|
// ip = await dnsProviders.py233.lookup(hostname1)
|
||||||
// console.log('ip:', ip)
|
// console.log('===> test py233:', ip, '\n\n')
|
||||||
|
|
||||||
// console.log('test ipaddress')
|
|
||||||
// ip = await dnsProviders.ipaddress.lookup(hostname0)
|
console.log('\n--------------- test TLS ---------------\n')
|
||||||
// console.log('ip:', ip)
|
ip = await dnsProviders.cloudflareTLS.lookup(presetHostname)
|
||||||
|
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:', ip, '\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.quad9TLS.dnsType, 'TLS')
|
||||||
|
// ip = await dnsProviders.quad9TLS.lookup(hostname1)
|
||||||
|
// console.log('===> test quad9TLS:', ip, '\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.aliyunTLS.dnsType, 'TLS')
|
||||||
|
// ip = await dnsProviders.aliyunTLS.lookup(hostname1)
|
||||||
|
// console.log('===> test aliyunTLS:', ip, '\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.aliyunTLS2.dnsType, 'TLS')
|
||||||
|
// ip = await dnsProviders.aliyunTLS2.lookup(hostname1)
|
||||||
|
// console.log('===> test aliyunTLS2:', ip, '\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.safe360TLS.dnsType, 'TLS')
|
||||||
|
// ip = await dnsProviders.safe360TLS.lookup(hostname1)
|
||||||
|
// console.log('===> test safe360TLS:', ip, '\n\n')
|
||||||
|
|
||||||
|
|
||||||
|
console.log('\n--------------- test TCP ---------------\n')
|
||||||
|
ip = await dnsProviders.googleTCP.lookup(presetHostname)
|
||||||
|
assert.strictEqual(ip, presetIp) // test preset
|
||||||
|
console.log('\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.googleTCP.dnsType, 'TCP')
|
||||||
|
// ip = await dnsProviders.googleTCP.lookup(hostname1)
|
||||||
|
// console.log('===> test googleTCP:', ip, '\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.aliyunTCP.dnsType, 'TCP')
|
||||||
|
// ip = await dnsProviders.aliyunTCP.lookup(hostname1)
|
||||||
|
// console.log('===> test aliyunTCP:', ip, '\n\n')
|
||||||
|
|
||||||
|
|
||||||
|
console.log('\n--------------- test UDP ---------------\n')
|
||||||
|
ip = await dnsProviders.googleUDP.lookup(presetHostname)
|
||||||
|
assert.strictEqual(ip, presetIp) // test preset
|
||||||
|
console.log('\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.googleUDP.dnsType, 'UDP')
|
||||||
|
// ip = await dnsProviders.googleUDP.lookup(hostname1)
|
||||||
|
// console.log('===> test googleUDP:', ip, '\n\n')
|
||||||
|
|
||||||
|
assert.strictEqual(dnsProviders.aliyunUDP.dnsType, 'UDP')
|
||||||
|
// ip = await dnsProviders.aliyunUDP.lookup(hostname1)
|
||||||
|
// console.log('===> test aliyunUDP:', ip, '\n\n')
|
||||||
|
|
Loading…
Reference in New Issue