fix: 修复站点监控使用自定义dns解析域名报错的bug

v2
xiaojunnuo 2025-07-31 10:44:50 +08:00
parent 3fc863561a
commit eb8cd53de2
4 changed files with 127 additions and 46 deletions

View File

@ -1,60 +1,134 @@
import { LocalCache } from '@certd/basic'; import {LocalCache, logger} from '@certd/basic';
import dnsSdk from 'dns' import dnsSdk, {AnyRecord} from 'dns'
import { LRUCache } from 'lru-cache';
import {LookupAddress} from "node:dns";
const dns = dnsSdk.promises const dns = dnsSdk.promises
export class DnsCustom{ export class DnsCustom{
resolver: any; private resolver: dnsSdk.promises.Resolver;
private cache = new LRUCache<string, any>({
max: 1000,
ttl: 1000 * 60 * 5,
});
constructor(dnsServers:string[]) { constructor(dnsServers:string[]) {
const resolver = new dns.Resolver(); const resolver = new dns.Resolver();
resolver.setServers(dnsServers); resolver.setServers(dnsServers);
this.resolver = resolver; this.resolver = resolver;
} }
async resolve(hostname:string,options:any):Promise<string[]>{
// { family: undefined, hints: 0, all: true }
const cnames = await this.resolver.resolveCname(hostname) async lookup(hostname:string,options?:{ family: any, hints: number, all: boolean }):Promise<LookupAddress[]>{
let cnameIps = [] const cacheKey = hostname + JSON.stringify(options)
let res = this.cache.get(cacheKey)
if (res){
return res
}
res = await this.doLookup(hostname,options)
this.cache.set(cacheKey,res)
return res
}
async doLookup(hostname:string,options?:{ family: any, hints: number, all: boolean }):Promise<LookupAddress[]>{
// { family: undefined, hints: 0, all: true }
let cnameIps:LookupAddress[] = []
let v4:LookupAddress[] = []
let v6:LookupAddress[] = []
let errors = []
const resolveCname = async ()=>{
let cnames = []
try{
cnames = await this.resolver.resolveCname(hostname)
}catch (e) {
errors.push(e)
logger.warn("query cname error",e)
}
// deep // deep
if (cnames && cnames.length > 0) { if (cnames && cnames.length > 0) {
for (let cname of cnames) { for (let cname of cnames) {
const cnameIp = await this.resolve(cname,options) try{
const cnameIp = await this.lookup(cname,options)
if (cnameIp && cnameIp.length > 0) { if (cnameIp && cnameIp.length > 0) {
cnameIps.push(...cnameIp) cnameIps.push(...cnameIp)
} }
}catch (e) {
errors.push(e)
logger.warn("lookup cname error",e)
} }
} }
let v4 = [] }
let v6 = [] }
const queryV6 = async ()=>{
try{
const list = await this.resolver.resolve6(hostname)
if (list && list.length > 0) {
v6 = list.map(item=>{
return {
address: item,
family: 6
}
})
}
}catch (e) {
logger.warn("query v6 error",e)
errors.push(e)
}
}
const queryV4 = async ()=>{
try{
const list =await this.resolver.resolve4(hostname)
if (list && list.length > 0) {
v4 = list.map(item=>{
return {
address: item,
family: 4
}
})
}
}catch (e) {
logger.warn("query v4 error",e)
errors.push(e)
}
}
const queries:Promise<any>[] = [resolveCname()]
const {family, all} = options const {family, all} = options
if(family === 6 && !all){
v6= await this.resolver.resolve6(hostname)
}
if(family === 4 && !all){
v4 = await this.resolver.resolve4(hostname)
}
if (all){ if (all){
v4 = await this.resolver.resolve4(hostname) queries.push(queryV6())
v6 = await this.resolver.resolve6(hostname) queries.push(queryV4())
}else{
if(family === 6 ){
queries.push(queryV6())
}
if(family === 4 ){
queries.push(queryV4())
}
}
await Promise.all(queries)
const res = [...v4,...v6,...cnameIps]
if(res.length === 0){
if (errors.length > 0){
const e = new Error(errors[0])
// @ts-ignore
e.errors = errors
throw e
}
}
return res
} }
return [...v4,...v6,...cnameIps] async resolve4(hostname:string):Promise<string[]>{
return await this.resolver.resolve4(hostname)
}
async resolve6(hostname:string):Promise<string[]>{
return await this.resolver.resolve6(hostname)
}
async resolveAny(hostname:string):Promise<AnyRecord[]>{
return await this.resolver.resolveAny(hostname)
} }
async resolve4(hostname:string,options:any):Promise<string[]>{ async resolveCname(hostname:string):Promise<string[]>{
return await this.resolver.resolve4(hostname,options) return await this.resolver.resolveCname(hostname)
}
async resolve6(hostname:string,options:any):Promise<string[]>{
return await this.resolver.resolve6(hostname,options)
}
async resolveAny(hostname:string,options:any):Promise<string[]>{
return await this.resolver.resolveAny(hostname,options)
}
async resolveCname(hostname:string,options:any):Promise<string[]>{
return await this.resolver.resolveCname(hostname,options)
} }

View File

@ -112,9 +112,9 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting); const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting);
const dnsServer = setting.dnsServer const dnsServer = setting.dnsServer
let resolver = null let customDns = null
if (dnsServer && dnsServer.length > 0) { if (dnsServer && dnsServer.length > 0) {
resolver = dnsContainer.getDns(dnsServer) as any customDns = dnsContainer.getDns(dnsServer) as any
} }
try { try {
@ -127,7 +127,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
host: site.domain, host: site.domain,
port: site.httpsPort, port: site.httpsPort,
retryTimes, retryTimes,
resolver customDns
}); });
const certi: PeerCertificate = res.certificate; const certi: PeerCertificate = res.certificate;
@ -154,7 +154,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
error: null, error: null,
checkStatus: "ok" checkStatus: "ok"
}; };
logger.info(`测试站点成功id=${updateData.id},site=${site.name},expiresTime=${updateData.certExpiresTime}`)
if (site.ipCheck) { if (site.ipCheck) {
delete updateData.checkStatus delete updateData.checkStatus
} }

View File

@ -134,6 +134,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
if (!entity) { if (!entity) {
return; return;
} }
logger.info(`开始测试站点ip: id=${entity.id},ip=${entity.ipAddress}`)
try { try {
await this.update({ await this.update({
id: entity.id, id: entity.id,
@ -173,7 +174,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
}; };
await this.update(updateData); await this.update(updateData);
logger.info(`测试站点ip成功: id=${updateData.id},ip=${entity.ipAddress},expiresTime=${updateData.certExpiresTime}`)
return updateData return updateData
} catch (e) { } catch (e) {
@ -231,7 +232,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
try{ try{
return await resolver.resolve4(domain); return await resolver.resolve4(domain);
}catch (err) { }catch (err) {
logger.error(`[${domain}] resolve4 error`, err) logger.warn(`[${domain}] resolve4 error`, err)
return [] return []
} }
} }
@ -239,7 +240,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
try{ try{
return await resolver.resolve6(domain); return await resolver.resolve6(domain);
}catch (err) { }catch (err) {
logger.error(`[${domain}] resolve6 error`, err) logger.warn(`[${domain}] resolve6 error`, err)
return [] return []
} }
} }

View File

@ -2,6 +2,7 @@ import { logger, safePromise, utils } from "@certd/basic";
import { merge } from "lodash-es"; import { merge } from "lodash-es";
import https from "https"; import https from "https";
import { PeerCertificate } from "tls"; import { PeerCertificate } from "tls";
import {DnsCustom} from "./dns-custom.js";
export type SiteTestReq = { export type SiteTestReq = {
host: string; // 只用域名部分 host: string; // 只用域名部分
@ -10,7 +11,7 @@ export type SiteTestReq = {
retryTimes?: number; retryTimes?: number;
ipAddress?: string; ipAddress?: string;
resolver?: any; customDns?: DnsCustom;
}; };
export type SiteTestRes = { export type SiteTestRes = {
@ -19,7 +20,9 @@ export type SiteTestRes = {
export class SiteTester { export class SiteTester {
async test(req: SiteTestReq): Promise<SiteTestRes> { async test(req: SiteTestReq): Promise<SiteTestRes> {
logger.info("测试站点:", JSON.stringify(req)); const req_ = {...req}
delete req_.customDns
logger.info("测试站点:", JSON.stringify(req_));
const maxRetryTimes = req.retryTimes == null ? 3 : req.retryTimes; const maxRetryTimes = req.retryTimes == null ? 3 : req.retryTimes;
let tryCount = 0; let tryCount = 0;
let result: SiteTestRes = {}; let result: SiteTestRes = {};
@ -61,15 +64,18 @@ export class SiteTester {
servername: options.host servername: options.host
}; };
options.host = ipAddress; options.host = ipAddress;
}else if (req.resolver ) { }else if (req.customDns ) {
// 非ip address 请求时 // 非ip address 请求时
const resolver = req.resolver const customDns = req.customDns
customLookup = async (hostname:string, options:any, callback)=> { customLookup = async (hostname:string, options:any, callback)=> {
console.log(hostname, options); console.log(hostname, options);
// { family: undefined, hints: 0, all: true } // { family: undefined, hints: 0, all: true }
const res = await resolver.resolve(hostname, options) const res = await customDns.lookup(hostname, options)
console.log("custom lookup res:",res) console.log("custom lookup res:",res)
if (!res || res.length === 0) {
callback(new Error("没有解析到IP"));
}
callback(null, res); callback(null, res);
} }
} }