126 lines
2.9 KiB
TypeScript
126 lines
2.9 KiB
TypeScript
import { CachedZone, DNSRecord, SOARecord } from '../models/interfaces';
|
|
import { readZoneFile } from './reader';
|
|
import { DNSRecordType } from './records';
|
|
import { ReloadExecutor } from './rndc';
|
|
import { ValidatorExecutor } from './validator';
|
|
|
|
export class DNSCache {
|
|
private cached: Record<string, CachedZone> = {};
|
|
|
|
constructor(
|
|
private rndc: ReloadExecutor,
|
|
private validator: ValidatorExecutor,
|
|
private ttl = 1600
|
|
) {}
|
|
|
|
has(name: string): boolean {
|
|
return this.cached[name] != null;
|
|
}
|
|
|
|
search(
|
|
cached: CachedZone,
|
|
name?: string,
|
|
type?: DNSRecordType,
|
|
value?: string,
|
|
strict = false
|
|
): DNSRecord[] {
|
|
return cached.zone.records.filter((zone) => {
|
|
if (type && zone.type !== type) {
|
|
return false;
|
|
}
|
|
|
|
if (name && zone.name !== name) {
|
|
return false;
|
|
}
|
|
|
|
if (value && ((!strict && !zone.value.includes(value as string)) ||
|
|
(strict && zone.value !== value))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}).map((record) => {
|
|
const inx = cached.zone.records.indexOf(record);
|
|
return {
|
|
...record,
|
|
index: inx
|
|
}
|
|
})
|
|
}
|
|
|
|
async get(name: string): Promise<CachedZone | null> {
|
|
const cached = this.cached[name];
|
|
if (!cached) {
|
|
return null;
|
|
}
|
|
|
|
if (cached.changed.getTime() < new Date().getTime() - this.ttl * 1000) {
|
|
return this.load(name, cached.file);
|
|
}
|
|
|
|
return this.cached[name];
|
|
}
|
|
|
|
async set(name: string, zone: CachedZone): Promise<void> {
|
|
this.cached[name] = zone;
|
|
}
|
|
|
|
async load(name: string, file: string): Promise<CachedZone> {
|
|
const zoneFile = await readZoneFile(file);
|
|
const cache = {
|
|
name,
|
|
file,
|
|
zone: zoneFile,
|
|
added: new Date(),
|
|
changed: new Date()
|
|
}
|
|
this.cached[name] = cache;
|
|
return cache;
|
|
}
|
|
|
|
async save(name: string): Promise<void> {
|
|
const zone = await this.get(name);
|
|
if (!zone) {
|
|
throw new Error('No such cached zone file!');
|
|
}
|
|
|
|
try {
|
|
await this.validator.validateAndSave(name, zone);
|
|
} catch (e) {
|
|
// Reload previous state
|
|
if (e.message.contains('Validation')) {
|
|
await this.load(name, zone.file);
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
async update(name: string, newZone?: CachedZone, skipReload = false): Promise<void> {
|
|
let zone: CachedZone | null;
|
|
if (newZone) {
|
|
zone = newZone;
|
|
} else {
|
|
zone = await this.get(name);
|
|
}
|
|
|
|
if (!zone) {
|
|
throw new Error('No such cached zone file!');
|
|
}
|
|
|
|
zone.changed = new Date();
|
|
const soa = zone.zone.records.find((record) => record.type === DNSRecordType.SOA) as SOARecord;
|
|
soa.serial = Math.floor(Date.now() / 1000);
|
|
|
|
this.set(name, zone);
|
|
await this.save(name);
|
|
|
|
if (!skipReload) {
|
|
try {
|
|
await this.rndc.reload(name);
|
|
} catch (e) {
|
|
console.warn('%s automatic zone reload failed:', name, e.stack);
|
|
}
|
|
}
|
|
}
|
|
}
|