parent
5bf7af6565
commit
07e2dff29f
@ -1,6 +1,6 @@
|
|||||||
import { Request, parseInput } from "./request";
|
import { Request, parseInput } from "./request";
|
||||||
import { RDATARecord, RecourceRecord } from "./record";
|
import { RDATARecord, RecourceRecord } from "./record";
|
||||||
import { MonitoringPlugin, QuestionPlugin, DnsCore, AnswerHandler, ListenerPlugin, StoragePlugin, Record } from ".";
|
import { MonitoringPlugin, QuestionPlugin, DnsCore, AnswerHandler, ListenerPlugin, StoragePlugin, Record } from "./core";
|
||||||
import assert = require("assert");
|
import assert = require("assert");
|
||||||
import { RecordTypes } from "./types";
|
import { RecordTypes } from "./types";
|
||||||
import { Question } from "./question";
|
import { Question } from "./question";
|
||||||
|
309
src/core.ts
Normal file
309
src/core.ts
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
import { Request } from "./request";
|
||||||
|
import { RecordTypes } from "./types";
|
||||||
|
import * as LRU from "lru-cache";
|
||||||
|
import { Writable } from "stream";
|
||||||
|
import { Question } from "./question";
|
||||||
|
import { RecourceRecord } from "./record";
|
||||||
|
|
||||||
|
// export class AnswerSteam extends Writable {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
export interface Record {
|
||||||
|
type: RecordTypes
|
||||||
|
domain: string
|
||||||
|
hostname: string
|
||||||
|
value: string
|
||||||
|
ttl: number
|
||||||
|
priority?: number
|
||||||
|
additional?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Plugin {
|
||||||
|
/**
|
||||||
|
* This sets the priority of this Plugin.
|
||||||
|
*
|
||||||
|
* The lower the value, the higher priority.
|
||||||
|
* It is recommended to make this value changeable over the cosntructor options field.
|
||||||
|
*/
|
||||||
|
Priority: number
|
||||||
|
|
||||||
|
init(core: DnsCore): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListenerPlugin extends Plugin {
|
||||||
|
/**
|
||||||
|
* This method is for registering a callback that is called
|
||||||
|
* when packet is received and ready for parsing.
|
||||||
|
* The function returns the Answer data. Errors shouldn't be possible
|
||||||
|
* by this function
|
||||||
|
*/
|
||||||
|
registerCallback(callback: (data: Buffer, sender: string, max_size?: number) => Promise<Buffer>): void
|
||||||
|
registerCallback(callback: (data: Buffer, sender: string, answer: Writable) => Promise<void>)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ListenerManager {
|
||||||
|
private _listener: ListenerPlugin[] = []
|
||||||
|
|
||||||
|
add(listener: ListenerPlugin) {
|
||||||
|
this._listener.push(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(core: DnsCore) {
|
||||||
|
return Promise.all(this._listener.map(e => e.init(core)))
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCallback(callback: (data: Buffer, sender: string, max_size?: number) => Promise<Buffer>) {
|
||||||
|
this._listener.forEach(e => e.registerCallback(callback))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MonitoringPlugin extends Plugin {
|
||||||
|
onRequest(domain: string, hostname: string, type: RecordTypes, additionalInformations): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MonitoringManager {
|
||||||
|
private _monitoring: MonitoringPlugin[] = []
|
||||||
|
|
||||||
|
add(monitoring: MonitoringPlugin) {
|
||||||
|
this._monitoring.push(monitoring)
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(core: DnsCore) {
|
||||||
|
return Promise.all(this._monitoring.map(e => e.init(core)))
|
||||||
|
}
|
||||||
|
|
||||||
|
onRequest(domain: string, hostname: string, type: RecordTypes, additionalInformations) {
|
||||||
|
this._monitoring.forEach(e => e.onRequest(domain, hostname, type, additionalInformations))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoragePlugin extends Plugin {
|
||||||
|
/**
|
||||||
|
* Here you can define wich record types this storage plugin is capable of.
|
||||||
|
*/
|
||||||
|
RecordTypes: RecordTypes[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables record caching for this Storage
|
||||||
|
*/
|
||||||
|
NoCache: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if storage plugin is responsible for this domain.
|
||||||
|
* This check is synchroneus because it should be quick.
|
||||||
|
*
|
||||||
|
* @param domain The domain for check
|
||||||
|
*/
|
||||||
|
isResponsible(domain: string): boolean
|
||||||
|
getRecords(domain: string, hostname: string, type: RecordTypes): Promise<Record[] | undefined>
|
||||||
|
getAllRecordsForDomain(domain: string): Promise<Record[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
type SortedStorage = {
|
||||||
|
[P in keyof any]: StoragePlugin[] | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheoptions: LRU.Options = {
|
||||||
|
length: () => 1,
|
||||||
|
max: 500,
|
||||||
|
maxAge: 10 * 60 * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecordCache {
|
||||||
|
private cache = new LRU<string, Record[]>(cacheoptions);
|
||||||
|
|
||||||
|
getRecords(domain: string, hostname: string, type: RecordTypes) {
|
||||||
|
let key = `${domain};;${hostname};;${RecordTypes[type]}`;
|
||||||
|
let rec = this.cache.get(key)
|
||||||
|
if (!rec) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
addRecords(domain: string, hostname: string, type: RecordTypes, records: Record[], ttl: number) {
|
||||||
|
let key = `${domain};;${hostname};;${RecordTypes[type]}`;
|
||||||
|
this.cache.set(key, records, ttl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StorageManager {
|
||||||
|
private _storages: StoragePlugin[] = []
|
||||||
|
private _sorted: SortedStorage;
|
||||||
|
private _cache = new RecordCache();
|
||||||
|
|
||||||
|
add(storage: StoragePlugin) {
|
||||||
|
this._storages.push(storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
private presort() {
|
||||||
|
this._storages = this._storages.sort((e1, e2) => e1.Priority - e2.Priority)
|
||||||
|
this._sorted = <any>{}
|
||||||
|
for (let key in RecordTypes) {
|
||||||
|
let key_n = Number(key)
|
||||||
|
if (key_n !== Number.NaN) {
|
||||||
|
//The _storages list is sorted with priority.
|
||||||
|
this._sorted[key_n] = this._storages.filter(e => e.RecordTypes.find(t => t === key_n) !== undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(core: DnsCore) {
|
||||||
|
this.presort()
|
||||||
|
return Promise.all(this._storages.map(e => e.init(core)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllRecordsForDomain(domain: string) {
|
||||||
|
return this._storages.find(e => e.isResponsible(domain)).getAllRecordsForDomain(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRecords(domain: string, hostname: string, type: RecordTypes) {
|
||||||
|
// First check if record is cached. Since the dns entries should not chacnge quickly
|
||||||
|
// using the cach for quicker response times is the way to go.
|
||||||
|
let records = this._cache.getRecords(domain, hostname, type);
|
||||||
|
if (records) return records;
|
||||||
|
|
||||||
|
let storages = this._sorted[type].filter(s => s.isResponsible(domain));
|
||||||
|
if (!storages || storages.length <= 0) return undefined
|
||||||
|
|
||||||
|
let nocache = false;
|
||||||
|
for (let storage of storages) {
|
||||||
|
nocache = storage.NoCache || nocache;
|
||||||
|
records = await storage.getRecords(domain, hostname, type)
|
||||||
|
if (records) {
|
||||||
|
if (storage.NoCache) this._cache.addRecords(domain, hostname, type, records, records.length <= 0 ? 3600 : records[0].ttl)
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of the checked storage engines that is
|
||||||
|
// responsible for the domain has the noCache flag,
|
||||||
|
// caching will be disabled for all.
|
||||||
|
if (!nocache)
|
||||||
|
this._cache.addRecords(domain, hostname, type, [], 0);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnswerHandler {
|
||||||
|
addQuestion: (question: Question) => void;
|
||||||
|
addAnswer: (record: RecourceRecord) => void;
|
||||||
|
addAdditional: (record: RecourceRecord) => void;
|
||||||
|
addAutority: (record: RecourceRecord) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QuestionPlugin extends Plugin {
|
||||||
|
/**
|
||||||
|
* This field defines wich question types this plugin is capable of handling.
|
||||||
|
*/
|
||||||
|
QuestionTypes: RecordTypes[];
|
||||||
|
init(core: DnsCore): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param question The question this plugin should solve
|
||||||
|
* @param request This gives the plugin access to relevant
|
||||||
|
* functions for queing depending questions or adding answers etc.
|
||||||
|
* @param next This function should be called if the plugin is not able to resolve
|
||||||
|
* the request. This will trigger other handlers.
|
||||||
|
*/
|
||||||
|
handleQuestion(question: Question, request: AnswerHandler, next: () => void): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class QuestionManager {
|
||||||
|
private _questions: QuestionPlugin[] = [];
|
||||||
|
private _sorted = <any>{};
|
||||||
|
add(question: QuestionPlugin) {
|
||||||
|
this._questions.push(question);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sort() {
|
||||||
|
this._questions = this._questions.sort((e1, e2) => e1.Priority - e2.Priority);
|
||||||
|
this._sorted = {};
|
||||||
|
for (let key in RecordTypes) {
|
||||||
|
let key_n = Number(key)
|
||||||
|
if (key_n !== Number.NaN) {
|
||||||
|
this._sorted[key_n] = this._questions.filter(e => e.QuestionTypes.find(t => t === key_n) !== undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(core: DnsCore) {
|
||||||
|
this.sort();
|
||||||
|
return Promise.all(this._questions.map(e => e.init(core)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleQuestion(question: Question, request: AnswerHandler) {
|
||||||
|
let handlers: QuestionPlugin[] = this._sorted[question.QTYPE];
|
||||||
|
if (!handlers || handlers.length <= 0) return;
|
||||||
|
let index = 0;
|
||||||
|
while (index < handlers.length) {
|
||||||
|
let i = index;
|
||||||
|
await handlers[i].handleQuestion(question, request, () => {
|
||||||
|
index++;
|
||||||
|
})
|
||||||
|
if (i === index) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DnsCore {
|
||||||
|
storageManager: StorageManager;
|
||||||
|
addStorage(plugin: StoragePlugin) {
|
||||||
|
this.storageManager.add(plugin);
|
||||||
|
}
|
||||||
|
monitoringManager: MonitoringManager;
|
||||||
|
addMonitoring(plugin: MonitoringPlugin) {
|
||||||
|
this.monitoringManager.add(plugin);
|
||||||
|
}
|
||||||
|
listenerManager: ListenerManager;
|
||||||
|
addListener(plugin: ListenerPlugin) {
|
||||||
|
this.listenerManager.add(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
questionManager: QuestionManager;
|
||||||
|
addQuestion(plugin: QuestionPlugin) {
|
||||||
|
this.questionManager.add(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.listenerManager = new ListenerManager()
|
||||||
|
this.monitoringManager = new MonitoringManager()
|
||||||
|
this.storageManager = new StorageManager()
|
||||||
|
this.questionManager = new QuestionManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
await this.storageManager.setup(this);
|
||||||
|
await this.monitoringManager.setup(this);
|
||||||
|
await this.listenerManager.setup(this);
|
||||||
|
await this.questionManager.setup(this);
|
||||||
|
this.listenerManager.registerCallback(this.onMessage.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onMessage(data: Buffer, sender: string, max_size: number = 0) {
|
||||||
|
let request = new Request(data, sender, max_size);
|
||||||
|
let questionqueue: Promise<void>[] = [];
|
||||||
|
let handleQuestion = question => {
|
||||||
|
let prom = this.questionManager.handleQuestion(question, {
|
||||||
|
addAdditional: record => {
|
||||||
|
request.addAdditionals(record)
|
||||||
|
},
|
||||||
|
addAnswer: record => {
|
||||||
|
request.addAnswer(record);
|
||||||
|
},
|
||||||
|
addAutority: record => {
|
||||||
|
request.addAuthorities(record);
|
||||||
|
},
|
||||||
|
addQuestion: question => {
|
||||||
|
handleQuestion(question)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
questionqueue.push(prom);
|
||||||
|
}
|
||||||
|
request.questions.map(q => handleQuestion(q));
|
||||||
|
await Promise.all(questionqueue);
|
||||||
|
return request.serialize();
|
||||||
|
}
|
||||||
|
}
|
313
src/index.ts
313
src/index.ts
@ -1,309 +1,4 @@
|
|||||||
import { Request } from "./request";
|
export { DnsCore, ListenerPlugin, MonitoringPlugin, StoragePlugin, QuestionPlugin, AnswerHandler, Record } from "./core"
|
||||||
import { RecordTypes } from "./types";
|
export { RecourceRecord, RDATARecord } from "./record"
|
||||||
import * as LRU from "lru-cache";
|
export { Request } from "./request"
|
||||||
import { Writable } from "stream";
|
export { Label } from "./label"
|
||||||
import { Question } from "./question";
|
|
||||||
import { RecourceRecord } from "./record";
|
|
||||||
|
|
||||||
// export class AnswerSteam extends Writable {
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
export interface Record {
|
|
||||||
type: RecordTypes
|
|
||||||
domain: string
|
|
||||||
hostname: string
|
|
||||||
value: string
|
|
||||||
ttl: number
|
|
||||||
priority?: number
|
|
||||||
additional?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Plugin {
|
|
||||||
/**
|
|
||||||
* This sets the priority of this Plugin.
|
|
||||||
*
|
|
||||||
* The lower the value, the higher priority.
|
|
||||||
* It is recommended to make this value changeable over the cosntructor options field.
|
|
||||||
*/
|
|
||||||
Priority: number
|
|
||||||
|
|
||||||
init(core: DnsCore): Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ListenerPlugin extends Plugin {
|
|
||||||
/**
|
|
||||||
* This method is for registering a callback that is called
|
|
||||||
* when packet is received and ready for parsing.
|
|
||||||
* The function returns the Answer data. Errors shouldn't be possible
|
|
||||||
* by this function
|
|
||||||
*/
|
|
||||||
registerCallback(callback: (data: Buffer, sender: string, max_size?: number) => Promise<Buffer>): void
|
|
||||||
registerCallback(callback: (data: Buffer, sender: string, answer: Writable) => Promise<void>)
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ListenerManager {
|
|
||||||
private _listener: ListenerPlugin[] = []
|
|
||||||
|
|
||||||
add(listener: ListenerPlugin) {
|
|
||||||
this._listener.push(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
setup(core: DnsCore) {
|
|
||||||
return Promise.all(this._listener.map(e => e.init(core)))
|
|
||||||
}
|
|
||||||
|
|
||||||
registerCallback(callback: (data: Buffer, sender: string, max_size?: number) => Promise<Buffer>) {
|
|
||||||
this._listener.forEach(e => e.registerCallback(callback))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MonitoringPlugin extends Plugin {
|
|
||||||
onRequest(domain: string, hostname: string, type: RecordTypes, additionalInformations): void
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MonitoringManager {
|
|
||||||
private _monitoring: MonitoringPlugin[] = []
|
|
||||||
|
|
||||||
add(monitoring: MonitoringPlugin) {
|
|
||||||
this._monitoring.push(monitoring)
|
|
||||||
}
|
|
||||||
|
|
||||||
setup(core: DnsCore) {
|
|
||||||
return Promise.all(this._monitoring.map(e => e.init(core)))
|
|
||||||
}
|
|
||||||
|
|
||||||
onRequest(domain: string, hostname: string, type: RecordTypes, additionalInformations) {
|
|
||||||
this._monitoring.forEach(e => e.onRequest(domain, hostname, type, additionalInformations))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StoragePlugin extends Plugin {
|
|
||||||
/**
|
|
||||||
* Here you can define wich record types this storage plugin is capable of.
|
|
||||||
*/
|
|
||||||
RecordTypes: RecordTypes[]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disables record caching for this Storage
|
|
||||||
*/
|
|
||||||
NoCache: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns if storage plugin is responsible for this domain.
|
|
||||||
* This check is synchroneus because it should be quick.
|
|
||||||
*
|
|
||||||
* @param domain The domain for check
|
|
||||||
*/
|
|
||||||
isResponsible(domain: string): boolean
|
|
||||||
getRecords(domain: string, hostname: string, type: RecordTypes): Promise<Record[] | undefined>
|
|
||||||
getAllRecordsForDomain(domain: string): Promise<Record[]>
|
|
||||||
}
|
|
||||||
|
|
||||||
type SortedStorage = {
|
|
||||||
[P in keyof any]: StoragePlugin[] | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const cacheoptions: LRU.Options = {
|
|
||||||
length: () => 1,
|
|
||||||
max: 500,
|
|
||||||
maxAge: 10 * 60 * 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
class RecordCache {
|
|
||||||
private cache = new LRU<string, Record[]>(cacheoptions);
|
|
||||||
|
|
||||||
getRecords(domain: string, hostname: string, type: RecordTypes) {
|
|
||||||
let key = `${domain};;${hostname};;${RecordTypes[type]}`;
|
|
||||||
let rec = this.cache.get(key)
|
|
||||||
if (!rec) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return rec;
|
|
||||||
}
|
|
||||||
|
|
||||||
addRecords(domain: string, hostname: string, type: RecordTypes, records: Record[], ttl: number) {
|
|
||||||
let key = `${domain};;${hostname};;${RecordTypes[type]}`;
|
|
||||||
this.cache.set(key, records, ttl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class StorageManager {
|
|
||||||
private _storages: StoragePlugin[] = []
|
|
||||||
private _sorted: SortedStorage;
|
|
||||||
private _cache = new RecordCache();
|
|
||||||
|
|
||||||
add(storage: StoragePlugin) {
|
|
||||||
this._storages.push(storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
private presort() {
|
|
||||||
this._storages = this._storages.sort((e1, e2) => e1.Priority - e2.Priority)
|
|
||||||
this._sorted = <any>{}
|
|
||||||
for (let key in RecordTypes) {
|
|
||||||
let key_n = Number(key)
|
|
||||||
if (key_n !== Number.NaN) {
|
|
||||||
//The _storages list is sorted with priority.
|
|
||||||
this._sorted[key_n] = this._storages.filter(e => e.RecordTypes.find(t => t === key_n) !== undefined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setup(core: DnsCore) {
|
|
||||||
this.presort()
|
|
||||||
return Promise.all(this._storages.map(e => e.init(core)))
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllRecordsForDomain(domain: string) {
|
|
||||||
return this._storages.find(e => e.isResponsible(domain)).getAllRecordsForDomain(domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getRecords(domain: string, hostname: string, type: RecordTypes) {
|
|
||||||
// First check if record is cached. Since the dns entries should not chacnge quickly
|
|
||||||
// using the cach for quicker response times is the way to go.
|
|
||||||
let records = this._cache.getRecords(domain, hostname, type);
|
|
||||||
if (records) return records;
|
|
||||||
|
|
||||||
let storages = this._sorted[type].filter(s => s.isResponsible(domain));
|
|
||||||
if (!storages || storages.length <= 0) return undefined
|
|
||||||
|
|
||||||
let nocache = false;
|
|
||||||
for (let storage of storages) {
|
|
||||||
nocache = storage.NoCache || nocache;
|
|
||||||
records = await storage.getRecords(domain, hostname, type)
|
|
||||||
if (records) {
|
|
||||||
if (storage.NoCache) this._cache.addRecords(domain, hostname, type, records, records.length <= 0 ? 3600 : records[0].ttl)
|
|
||||||
return records;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any of the checked storage engines that is
|
|
||||||
// responsible for the domain has the noCache flag,
|
|
||||||
// caching will be disabled for all.
|
|
||||||
if (!nocache)
|
|
||||||
this._cache.addRecords(domain, hostname, type, [], 0);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AnswerHandler {
|
|
||||||
addQuestion: (question: Question) => void;
|
|
||||||
addAnswer: (record: RecourceRecord) => void;
|
|
||||||
addAdditional: (record: RecourceRecord) => void;
|
|
||||||
addAutority: (record: RecourceRecord) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface QuestionPlugin extends Plugin {
|
|
||||||
/**
|
|
||||||
* This field defines wich question types this plugin is capable of handling.
|
|
||||||
*/
|
|
||||||
QuestionTypes: RecordTypes[];
|
|
||||||
init(core: DnsCore): Promise<void>
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param question The question this plugin should solve
|
|
||||||
* @param request This gives the plugin access to relevant
|
|
||||||
* functions for queing depending questions or adding answers etc.
|
|
||||||
* @param next This function should be called if the plugin is not able to resolve
|
|
||||||
* the request. This will trigger other handlers.
|
|
||||||
*/
|
|
||||||
handleQuestion(question: Question, request: AnswerHandler, next: () => void): Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
export class QuestionManager {
|
|
||||||
private _questions: QuestionPlugin[] = [];
|
|
||||||
private _sorted = <any>{};
|
|
||||||
add(question: QuestionPlugin) {
|
|
||||||
this._questions.push(question);
|
|
||||||
}
|
|
||||||
|
|
||||||
private sort() {
|
|
||||||
this._questions = this._questions.sort((e1, e2) => e1.Priority - e2.Priority);
|
|
||||||
this._sorted = {};
|
|
||||||
for (let key in RecordTypes) {
|
|
||||||
let key_n = Number(key)
|
|
||||||
if (key_n !== Number.NaN) {
|
|
||||||
this._sorted[key_n] = this._questions.filter(e => e.QuestionTypes.find(t => t === key_n) !== undefined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setup(core: DnsCore) {
|
|
||||||
this.sort();
|
|
||||||
return Promise.all(this._questions.map(e => e.init(core)))
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleQuestion(question: Question, request: AnswerHandler) {
|
|
||||||
let handlers: QuestionPlugin[] = this._sorted[question.QTYPE];
|
|
||||||
if (!handlers || handlers.length <= 0) return;
|
|
||||||
let index = 0;
|
|
||||||
while (index < handlers.length) {
|
|
||||||
let i = index;
|
|
||||||
await handlers[i].handleQuestion(question, request, () => {
|
|
||||||
index++;
|
|
||||||
})
|
|
||||||
if (i === index) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DnsCore {
|
|
||||||
storageManager: StorageManager;
|
|
||||||
addStorage(plugin: StoragePlugin) {
|
|
||||||
this.storageManager.add(plugin);
|
|
||||||
}
|
|
||||||
monitoringManager: MonitoringManager;
|
|
||||||
addMonitoring(plugin: MonitoringPlugin) {
|
|
||||||
this.monitoringManager.add(plugin);
|
|
||||||
}
|
|
||||||
listenerManager: ListenerManager;
|
|
||||||
addListener(plugin: ListenerPlugin) {
|
|
||||||
this.listenerManager.add(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
questionManager: QuestionManager;
|
|
||||||
addQuestion(plugin: QuestionPlugin) {
|
|
||||||
this.questionManager.add(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.listenerManager = new ListenerManager()
|
|
||||||
this.monitoringManager = new MonitoringManager()
|
|
||||||
this.storageManager = new StorageManager()
|
|
||||||
this.questionManager = new QuestionManager()
|
|
||||||
}
|
|
||||||
|
|
||||||
async start() {
|
|
||||||
await this.storageManager.setup(this);
|
|
||||||
await this.monitoringManager.setup(this);
|
|
||||||
await this.listenerManager.setup(this);
|
|
||||||
await this.questionManager.setup(this);
|
|
||||||
this.listenerManager.registerCallback(this.onMessage.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async onMessage(data: Buffer, sender: string, max_size: number = 0) {
|
|
||||||
let request = new Request(data, sender, max_size);
|
|
||||||
let questionqueue: Promise<void>[] = [];
|
|
||||||
let handleQuestion = question => {
|
|
||||||
let prom = this.questionManager.handleQuestion(question, {
|
|
||||||
addAdditional: record => {
|
|
||||||
request.addAdditionals(record)
|
|
||||||
},
|
|
||||||
addAnswer: record => {
|
|
||||||
request.addAnswer(record);
|
|
||||||
},
|
|
||||||
addAutority: record => {
|
|
||||||
request.addAuthorities(record);
|
|
||||||
},
|
|
||||||
addQuestion: question => {
|
|
||||||
handleQuestion(question)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
questionqueue.push(prom);
|
|
||||||
}
|
|
||||||
request.questions.map(q => handleQuestion(q));
|
|
||||||
await Promise.all(questionqueue);
|
|
||||||
return request.serialize();
|
|
||||||
}
|
|
||||||
}
|
|
@ -338,7 +338,7 @@ describe("parser", function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
import { DnsCore, StoragePlugin, Record, MonitoringPlugin, QuestionPlugin, AnswerHandler, ListenerPlugin } from "./index"
|
import { DnsCore, StoragePlugin, Record, MonitoringPlugin, QuestionPlugin, AnswerHandler, ListenerPlugin } from "./core"
|
||||||
|
|
||||||
describe("DNS Core", function () {
|
describe("DNS Core", function () {
|
||||||
it("Initialization", () => {
|
it("Initialization", () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user