import { CachedZone, 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; } 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!'); } await this.validator.validateAndSave(name, zone); } 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); } } } }