perf: 证书检查支持自定义dns服务器

This commit is contained in:
xiaojunnuo
2025-07-07 00:10:51 +08:00
parent 0cea26c628
commit c53bb7cf67
13 changed files with 217 additions and 66 deletions
@@ -27,6 +27,7 @@ export class UserSiteMonitorSetting extends BaseSettings {
notificationId?:number= 0;
cron?:string = undefined;
retryTimes?:number = 3;
dnsServer?:string[] = undefined;
}
export class UserEmailSetting extends BaseSettings {
@@ -3,8 +3,13 @@ import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { BaseService, BaseSettings } from "@certd/lib-server";
import { UserSettingsEntity } from "../entity/user-settings.js";
import { mergeUtils } from "@certd/basic";
import { LocalCache, mergeUtils } from "@certd/basic";
const {merge} = mergeUtils
const UserSettingCache = new LocalCache({
clearInterval: 5 * 60 * 1000,
});
/**
* 授权
*/
@@ -75,14 +80,26 @@ export class UserSettingsService extends BaseService<UserSettingsEntity> {
}
async getSetting<T>( userId: number,type: any): Promise<T> {
async getSetting<T>( userId: number,type: any, cache:boolean = false): Promise<T> {
if(!userId){
throw new Error('userId is required');
}
const key = type.__key__;
const cacheKey = key + '_' + userId;
if (cache) {
const settings: T = UserSettingCache.get(cacheKey);
if (settings) {
return settings;
}
}
let newSetting: T = new type();
const savedSettings = await this.getSettingByKey(key, userId);
newSetting = merge(newSetting, savedSettings);
if (cache) {
UserSettingCache.set(cacheKey, newSetting);
}
return newSetting;
}
@@ -0,0 +1,79 @@
import { LocalCache } from '@certd/basic';
import dnsSdk from 'dns'
const dns = dnsSdk.promises
export class DnsCustom{
resolver: any;
constructor(dnsServers:string[]) {
const resolver = new dns.Resolver();
resolver.setServers(dnsServers);
this.resolver = resolver;
}
async resolve(hostname:string,options:any):Promise<string[]>{
// { family: undefined, hints: 0, all: true }
const cnames = await this.resolver.resolveCname(hostname)
let cnameIps = []
// deep
if (cnames && cnames.length > 0) {
for (let cname of cnames) {
const cnameIp = await this.resolve(cname,options)
if (cnameIp && cnameIp.length > 0) {
cnameIps.push(...cnameIp)
}
}
}
let v4 = []
let v6 = []
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){
v4 = await this.resolver.resolve4(hostname)
v6 = await this.resolver.resolve6(hostname)
}
return [...v4,...v6,...cnameIps]
}
async resolve4(hostname:string,options:any):Promise<string[]>{
return await this.resolver.resolve4(hostname,options)
}
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)
}
}
export class DnsContainer{
bucket: LocalCache<DnsCustom> = new LocalCache()
constructor() {}
getDns(server:string[]){
const key = server.join(',')
let dns = this.bucket.get(key)
if (dns){
return dns
}
dns = new DnsCustom(server)
this.bucket.set(key,dns)
return dns
}
}
export const dnsContainer = new DnsContainer()
@@ -15,6 +15,7 @@ import {UserSiteMonitorSetting} from "../../mine/service/models.js";
import {SiteIpService} from "./site-ip-service.js";
import {SiteIpEntity} from "../entity/site-ip.js";
import {Cron} from "../../cron/cron.js";
import { dnsContainer } from "./dns-custom.js";
@Provide()
@Scope(ScopeEnum.Request, {allowDowngrade: true})
@@ -108,6 +109,14 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
if (!site?.domain) {
throw new Error("站点域名不能为空");
}
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting);
const dnsServer = setting.dnsServer
let resolver = null
if (dnsServer && dnsServer.length > 0) {
resolver = dnsContainer.getDns(dnsServer) as any
}
try {
await this.update({
id: site.id,
@@ -117,7 +126,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
const res = await siteTester.test({
host: site.domain,
port: site.httpsPort,
retryTimes
retryTimes,
resolver
});
const certi: PeerCertificate = res.certificate;
@@ -7,11 +7,15 @@ import {NotificationService} from "../../pipeline/service/notification-service.j
import {UserSuiteService} from "@certd/commercial-core";
import {UserSettingsService} from "../../mine/service/user-settings-service.js";
import {SiteIpEntity} from "../entity/site-ip.js";
import dns from "dns";
import {logger, safePromise} from "@certd/basic";
import dnsSdk from "dns";
import {logger} from "@certd/basic";
import dayjs from "dayjs";
import {siteTester} from "./site-tester.js";
import {PeerCertificate} from "tls";
import { UserSiteMonitorSetting } from "../../mine/service/models.js";
import { dnsContainer } from "./dns-custom.js";
const dns = dnsSdk.promises;
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
@@ -62,11 +66,20 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
async sync(entity: SiteInfoEntity,check:boolean = true) {
const domain = entity.domain;
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(entity.userId, UserSiteMonitorSetting);
const dnsServer = setting.dnsServer
let resolver = dns
if (dnsServer && dnsServer.length > 0) {
resolver = dnsContainer.getDns(dnsServer) as any
}
//从域名解析中获取所有ip
const ips = await this.getAllIpsFromDomain(domain);
const ips = await this.getAllIpsFromDomain(domain,resolver);
if (ips.length === 0 ) {
logger.warn(`没有发现${domain}的IP`)
return
return
}
const oldIps = await this.repository.find({
@@ -86,7 +99,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
hasChanged = false
}
}
if(hasChanged){
logger.info(`发现${domain}的IP变化,需要更新,旧IP:${oldIps.map(ip=>ip.ipAddress).join(",")},新IP:${ips.join(",")}`)
//有变化需要更新
@@ -213,30 +226,26 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
})
}
async getAllIpsFromDomain(domain: string) {
const getFromV4 = safePromise<string[]>((resolve, reject) => {
dns.resolve4(domain, (err, addresses) => {
if (err) {
logger.error(`[${domain}] resolve4 error`, err)
resolve([])
return;
}
resolve(addresses);
});
});
async getAllIpsFromDomain(domain: string,resolver:any = dns):Promise<string[]> {
const getFromV4 = async ():Promise<string[]> => {
try{
return await resolver.resolve4(domain);
}catch (err) {
logger.error(`[${domain}] resolve4 error`, err)
return []
}
}
const getFromV6 = async ():Promise<string[]> => {
try{
return await resolver.resolve6(domain);
}catch (err) {
logger.error(`[${domain}] resolve6 error`, err)
return []
}
}
const getFromV6 = safePromise<string[]>((resolve, reject) => {
dns.resolve6(domain, (err, addresses) => {
if (err) {
logger.error("[${domain}] resolve6 error", err)
resolve([])
return;
}
resolve(addresses);
});
});
return Promise.all([getFromV4, getFromV6]).then(res => {
return Promise.all([getFromV4(), getFromV6()]).then(res => {
return [...res[0], ...res[1]];
});
}
@@ -2,7 +2,6 @@ import { logger, safePromise, utils } from "@certd/basic";
import { merge } from "lodash-es";
import https from "https";
import { PeerCertificate } from "tls";
// import { TCPClient } from "dns2";
export type SiteTestReq = {
host: string; // 只用域名部分
@@ -11,7 +10,7 @@ export type SiteTestReq = {
retryTimes?: number;
ipAddress?: string;
dnsServer?: string[];
resolver?: any;
};
export type SiteTestRes = {
@@ -52,6 +51,7 @@ export class SiteTester {
req
);
let customLookup = null
if (req.ipAddress) {
//使用固定的ip
const ipAddress = req.ipAddress;
@@ -61,37 +61,20 @@ export class SiteTester {
servername: options.host
};
options.host = ipAddress;
}else if (req.resolver ) {
// 非ip address 请求时
const resolver = req.resolver
customLookup = async (hostname:string, options:any, callback)=> {
console.log(hostname, options);
// { family: undefined, hints: 0, all: true }
const res = await resolver.resolve(hostname, options)
console.log("custom lookup res:",res)
callback(null, res);
}
}
// let dnsClients = [];
// if (req.dnsServer && req.dnsServer.length > 0) {
// for (let dns of req.dnsServer) {
// const dnsClient = TCPClient({ dns });
// dnsClients.push(dnsClient);
// }
// }
// async function customLookup(hostname, options, callback) {
// for (let client of dnsClients) {
// try {
// const result = await client.resolve(hostname, options);
// return callback(null, result);
// } catch (e) {
// this.logger.error(e);
// }
// }
// try {
// // 使用自定义DNS解析
// const response = await dnsClients
// const address = response.answers[0].address;
// callback(null, address, 4);
// } catch (err) {
// // 解析失败时回退到系统DNS
// require('dns').lookup(hostname, options, callback);
// }
// }
options.agent = new https.Agent({ keepAlive: false });
options.agent = new https.Agent({ keepAlive: false, lookup: customLookup });
// 创建 HTTPS 请求
const requestPromise = safePromise((resolve, reject) => {