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 = {}; 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 { 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 { this.cached[name] = zone; } async load(name: string, file: string): Promise { 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 { 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 { 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); } } } }