feature: DoH类型的DNS,已支持SNI

release-2.0.0.2
王良 2025-05-15 15:44:58 +08:00
parent b582ac63ad
commit 4a3cae392f
7 changed files with 132 additions and 20 deletions

View File

@ -103,19 +103,21 @@ module.exports = class BaseDNS {
}
async _lookupWithPreSetIpList (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 (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-PreSet '${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
}
}
}
@ -159,6 +161,10 @@ module.exports = class BaseDNS {
}
_doDnsQuery (hostname, type = 'A', start) {
if (start == null) {
start = Date.now()
}
return new Promise((resolve, reject) => {
// 设置超时任务
let isOver = false

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: 20000,
})
}
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
}
_dnsQueryPromise (hostname, type = 'A') {
return dohQueryAsync({ url: this.dnsServer }, [{ type, name: hostname }])
// 请求参数
const options = {
url: this.dnsServer,
agent: createAgent(this.dnsServer),
}
if (this.dnsServerName) {
// 设置SNI
options.servername = this.dnsServerName
options.rejectUnauthorized = false
}
// DNS查询参数
const questions = [
{
type,
name: hostname,
},
]
return dohQueryAsync(options, questions)
}
}

View File

@ -48,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
@ -64,7 +64,7 @@ module.exports = {
if (type === 'tls' || type === 'dot' || type === 'dns-over-tls') {
// 基于 tls
dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.servername || conf.sni)
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)

View File

@ -9,7 +9,7 @@ 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
}

View File

@ -6,7 +6,7 @@ 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
}

View File

@ -8,7 +8,7 @@ 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(']')

View File

@ -0,0 +1,77 @@
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://1.1.1.1/dns-query',
'https://dns.cloudflare.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 hostname1 = 'github.com'
const sni = 'baidu.com'
console.log(`\n--------------- 测试DoH的SNI功能${servers.length} 个DoH服务 ---------------\n`)
let n = 0
let success = 0
let error = 0
const arr = []
function count (isSuccess, i, doh, result) {
n++
if (isSuccess) {
success++
arr[i] = `${doh.dnsServer} : ${hostname1} -> ${result.answers[0].data}`;
} else error++
if (n === servers.length) {
console.info(`\n\n=============================================================================\n全部测完:总计:${servers.length}, 成功:${success},失败:${error}`);
for (const item of arr) {
if (item) {
console.info(item);
}
}
console.info('=============================================================================\n\n')
}
}
for (let i = 0; i < servers.length; i++) {
const n = i;
const doh = new DNSOverHTTPS(`dns${i}`, null, null, servers[i], sni)
doh._doDnsQuery(hostname1)
.then((result) => {
// console.info(`===> test testDoH '${doh.dnsServer}': ${hostname1} ->`, result.answers, '\n\n')
count(true, n, doh, result)
})
.catch((e) => {
// console.error(`===> test testDoH '${doh.dnsServer}': ${hostname1} 失败:`, e, '\n\n')
count(false)
})
}