This repository has been archived on 2024-04-14. You can view files and clone it, but cannot push or open issues or pull requests.
icydns/src/dns/cache.ts

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);
}
}
}
}