mirror of https://github.com/certd/certd
fix: 修复站点监控使用自定义dns解析域名报错的bug
parent
3fc863561a
commit
eb8cd53de2
|
@ -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)
|
||||||
// deep
|
let res = this.cache.get(cacheKey)
|
||||||
if (cnames && cnames.length > 0) {
|
if (res){
|
||||||
for (let cname of cnames) {
|
return res
|
||||||
const cnameIp = await this.resolve(cname,options)
|
}
|
||||||
if (cnameIp && cnameIp.length > 0) {
|
res = await this.doLookup(hostname,options)
|
||||||
cnameIps.push(...cnameIp)
|
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
|
||||||
|
if (cnames && cnames.length > 0) {
|
||||||
|
for (let cname of cnames) {
|
||||||
|
try{
|
||||||
|
const cnameIp = await this.lookup(cname,options)
|
||||||
|
if (cnameIp && cnameIp.length > 0) {
|
||||||
|
cnameIps.push(...cnameIp)
|
||||||
|
}
|
||||||
|
}catch (e) {
|
||||||
|
errors.push(e)
|
||||||
|
logger.warn("lookup cname error",e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let v4 = []
|
const queryV6 = async ()=>{
|
||||||
let v6 = []
|
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){
|
if (all){
|
||||||
v6= await this.resolver.resolve6(hostname)
|
queries.push(queryV6())
|
||||||
|
queries.push(queryV4())
|
||||||
|
}else{
|
||||||
|
if(family === 6 ){
|
||||||
|
queries.push(queryV6())
|
||||||
|
}
|
||||||
|
if(family === 4 ){
|
||||||
|
queries.push(queryV4())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(family === 4 && !all){
|
await Promise.all(queries)
|
||||||
v4 = await this.resolver.resolve4(hostname)
|
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
|
||||||
if(all){
|
|
||||||
v4 = await this.resolver.resolve4(hostname)
|
|
||||||
v6 = await this.resolver.resolve6(hostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...v4,...v6,...cnameIps]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolve4(hostname:string,options:any):Promise<string[]>{
|
async resolve4(hostname:string):Promise<string[]>{
|
||||||
return await this.resolver.resolve4(hostname,options)
|
return await this.resolver.resolve4(hostname)
|
||||||
}
|
}
|
||||||
async resolve6(hostname:string,options:any):Promise<string[]>{
|
async resolve6(hostname:string):Promise<string[]>{
|
||||||
return await this.resolver.resolve6(hostname,options)
|
return await this.resolver.resolve6(hostname)
|
||||||
}
|
}
|
||||||
async resolveAny(hostname:string,options:any):Promise<string[]>{
|
async resolveAny(hostname:string):Promise<AnyRecord[]>{
|
||||||
return await this.resolver.resolveAny(hostname,options)
|
return await this.resolver.resolveAny(hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveCname(hostname:string,options:any):Promise<string[]>{
|
async resolveCname(hostname:string):Promise<string[]>{
|
||||||
return await this.resolver.resolveCname(hostname,options)
|
return await this.resolver.resolveCname(hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue