This commit is contained in:
parent
69094524d1
commit
5bf7af6565
32
Readme.md
Normal file
32
Readme.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# NodeName-Core
|
||||||
|
This repository is part of the NodeName Project, a simple to use and very flexible Nameserver implementation for NodeJS.
|
||||||
|
|
||||||
|
## What does this NodeName-Core contain?
|
||||||
|
|
||||||
|
NodeName-Core is responsible for several things:
|
||||||
|
|
||||||
|
- parsing incoming DNS requests
|
||||||
|
- serializing outgoing answers
|
||||||
|
- providing storage backend API definition
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
``` JavaScript
|
||||||
|
const Core = new DnsCore();
|
||||||
|
Core.addStorage(new AnyStoragePlugin(options), timeout?);
|
||||||
|
// One or multiple storage plugins
|
||||||
|
// All Storage engines are requested at the same time all their results will be processed
|
||||||
|
// Optionally an timeout can be defined after wich this storage engine will be ignored
|
||||||
|
// If no others answer an error is returned in Response
|
||||||
|
|
||||||
|
Core.addMonitoring(new MonitoringPlugin(options));
|
||||||
|
// Async called Monmitoring plugin.
|
||||||
|
|
||||||
|
Core.addListener(new ListenetPlugin(options));
|
||||||
|
|
||||||
|
// The question plugin can handle on or more Questions and needs to
|
||||||
|
// make shure, that it creates the desired output. It is responsible
|
||||||
|
// for handeling the question and optionally add additional questions
|
||||||
|
// to be resolved.
|
||||||
|
Core.addQuestionHandler(new QuestionPlugin(options));
|
||||||
|
```
|
@ -28,4 +28,4 @@
|
|||||||
"binary-parser": "^1.3.2",
|
"binary-parser": "^1.3.2",
|
||||||
"lru-cache": "^5.1.1"
|
"lru-cache": "^5.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
171
src/benchmark.ts
Normal file
171
src/benchmark.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import { Request, parseInput } from "./request";
|
||||||
|
import { RDATARecord, RecourceRecord } from "./record";
|
||||||
|
import { MonitoringPlugin, QuestionPlugin, DnsCore, AnswerHandler, ListenerPlugin, StoragePlugin, Record } from ".";
|
||||||
|
import assert = require("assert");
|
||||||
|
import { RecordTypes } from "./types";
|
||||||
|
import { Question } from "./question";
|
||||||
|
|
||||||
|
function fromHex(data: string) {
|
||||||
|
return Buffer.from(data.replace(/\s/g, ""), "hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
const itr = 100000;
|
||||||
|
console.log("All tests x" + itr);
|
||||||
|
|
||||||
|
let reqData = fromHex("E835 0100 0001 0000 0000 0000 07 6578616D706c65 03636F6D 00 0001 0001");
|
||||||
|
console.time("input parser")
|
||||||
|
for (let i = 0; i < itr; i++) {
|
||||||
|
parseInput(reqData);
|
||||||
|
}
|
||||||
|
console.timeEnd("input parser")
|
||||||
|
|
||||||
|
console.time("complete")
|
||||||
|
for (let i = 0; i < itr; i++) {
|
||||||
|
let request = new Request(reqData, "")
|
||||||
|
let rr = new RDATARecord()
|
||||||
|
rr.CLASS = 1
|
||||||
|
rr.NAME = "example.com"
|
||||||
|
rr.TTL = 1600
|
||||||
|
rr.TYPE = 1
|
||||||
|
rr.RDATA = fromHex("0A 00 00 01")
|
||||||
|
request.addAnswer(rr)
|
||||||
|
request.serialize()
|
||||||
|
}
|
||||||
|
console.timeEnd("complete")
|
||||||
|
|
||||||
|
console.time("complete 10 answers")
|
||||||
|
for (let i = 0; i < itr; i++) {
|
||||||
|
let request = new Request(reqData, "")
|
||||||
|
let rr = new RDATARecord()
|
||||||
|
rr.CLASS = 1
|
||||||
|
rr.NAME = "example.com"
|
||||||
|
rr.TTL = 1600
|
||||||
|
rr.TYPE = 1
|
||||||
|
rr.RDATA = fromHex("0A 00 00 01")
|
||||||
|
for (let i = 0; i < 10; i++) request.addAnswer(rr);
|
||||||
|
request.serialize()
|
||||||
|
}
|
||||||
|
console.timeEnd("complete 10 answers")
|
||||||
|
|
||||||
|
class TestMonitoringPlugin implements MonitoringPlugin {
|
||||||
|
constructor() { }
|
||||||
|
async init(core) { }
|
||||||
|
Priority = 0
|
||||||
|
onRequest(domain, hostname, type) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
class ARR extends RecourceRecord {
|
||||||
|
TYPE = RecordTypes.A
|
||||||
|
|
||||||
|
constructor(domain: string, ip: string) {
|
||||||
|
super();
|
||||||
|
this.NAME = domain
|
||||||
|
this.TTL = 1600
|
||||||
|
let data = Buffer.alloc(4)
|
||||||
|
let idx = 0;
|
||||||
|
ip.split(".").forEach(e => {
|
||||||
|
data.writeUInt8(Number(e), idx);
|
||||||
|
idx++;
|
||||||
|
})
|
||||||
|
this.dataLock = () => {
|
||||||
|
return {
|
||||||
|
length: 4,
|
||||||
|
serialize: (buffer, offset) => {
|
||||||
|
data.copy(buffer, offset);
|
||||||
|
return offset + 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestQuestionPlugin implements QuestionPlugin {
|
||||||
|
private Core: DnsCore
|
||||||
|
QuestionTypes = [RecordTypes.A]
|
||||||
|
Priority = 0
|
||||||
|
async init(core) {
|
||||||
|
this.Core = core;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleQuestion(question: Question, request: AnswerHandler, next: () => void) {
|
||||||
|
assert(this.QuestionTypes.find(e => e === <any>question.QTYPE), "Handler was called with not supported question type")
|
||||||
|
let parts = question.QNAME.split(".")
|
||||||
|
let domain = parts.splice(-2, 2).join(".")
|
||||||
|
let hostname = parts.join(".");
|
||||||
|
hostname = hostname !== "" ? hostname : "*"
|
||||||
|
let records = await this.Core.storageManager.getRecords(domain, hostname, question.QTYPE)
|
||||||
|
records.forEach(e => request.addAnswer(new ARR(question.QNAME, e.value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestListenerPlugin implements ListenerPlugin {
|
||||||
|
async init(core) { }
|
||||||
|
Priority = 0;
|
||||||
|
callback: (data: Buffer, sender: string, max_size?: number) => Promise<Buffer>;
|
||||||
|
registerCallback(callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRequest(message: Buffer) {
|
||||||
|
return this.callback(message, "localhost")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestStoragePlugin implements StoragePlugin {
|
||||||
|
re: Record = { domain: "example.com", hostname: "test", type: RecordTypes.A, value: "10.0.0.1", ttl: 1600 }
|
||||||
|
|
||||||
|
constructor(record?: Record, private count: number = 1) {
|
||||||
|
if (record)
|
||||||
|
this.re = record;
|
||||||
|
}
|
||||||
|
|
||||||
|
Priority = 0
|
||||||
|
NoCache = false
|
||||||
|
RecordTypes = [RecordTypes.A]
|
||||||
|
isResponsible(domain: string) {
|
||||||
|
// console.log("Is responsible")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(core) { }
|
||||||
|
|
||||||
|
async getRecords(domain, hostname, type) {
|
||||||
|
if (domain !== this.re.domain || hostname !== this.re.hostname || type != this.re.type) return undefined;
|
||||||
|
let r = [];
|
||||||
|
for (let i = 0; i < this.count; i++) {
|
||||||
|
r.push(this.re);
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllRecordsForDomain(domain) {
|
||||||
|
if (domain !== this.re.domain) return undefined;
|
||||||
|
return [this.re];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let core = new DnsCore();
|
||||||
|
|
||||||
|
let listener = new TestListenerPlugin();
|
||||||
|
core.addListener(listener)
|
||||||
|
core.addMonitoring(new TestMonitoringPlugin());
|
||||||
|
core.addQuestion(new TestQuestionPlugin())
|
||||||
|
|
||||||
|
let storage = new TestStoragePlugin({
|
||||||
|
domain: "example.com",
|
||||||
|
hostname: "test",
|
||||||
|
type: RecordTypes.A,
|
||||||
|
ttl: 500,
|
||||||
|
value: "10.0.0.1"
|
||||||
|
}, 3)
|
||||||
|
core.addStorage(storage);
|
||||||
|
|
||||||
|
core.start().then(async () => {
|
||||||
|
let reqData = fromHex("E835 0100 0001 0000 0000 0000 04 74657374 07 6578616D706c65 03 636F6D 00 0001 0001");
|
||||||
|
console.time("core handler");
|
||||||
|
for (let i = 0; i < itr; i++) {
|
||||||
|
await listener.sendRequest(reqData)
|
||||||
|
}
|
||||||
|
console.timeEnd("core handler");
|
||||||
|
});
|
312
src/index.ts
312
src/index.ts
@ -1,7 +1,309 @@
|
|||||||
import * as Types from "./types";
|
import { Request } from "./request";
|
||||||
import * as 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 = {
|
// export class AnswerSteam extends Writable {
|
||||||
types: Types,
|
|
||||||
request: Request
|
// }
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { Parser } from "binary-parser"
|
import { Parser } from "binary-parser"
|
||||||
import { IMessageHeader, IMessageQuestion, MessageRecourceRecord, ErrorCodes } from "./types"
|
import { IMessageHeader, IMessageQuestion, MessageRecourceRecord, ErrorCodes } from "./types"
|
||||||
import { Serializer } from "./serializeable";
|
import { Serializer, Serializeable } from "./serializeable";
|
||||||
import { Question } from "./question";
|
import { Question } from "./question";
|
||||||
import { RecourceRecord } from "./record";
|
import { RecourceRecord } from "./record";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
@ -68,12 +68,23 @@ export class Request {
|
|||||||
|
|
||||||
private _questions: Question[];
|
private _questions: Question[];
|
||||||
get questions() {
|
get questions() {
|
||||||
return this._questions.map(e => Object.assign({}, e));
|
return this._questions.map(e => new Question(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
public _answers: RecourceRecord[] = [];
|
private _answers: Serializer[] = [];
|
||||||
public _authorities: RecourceRecord[] = [];
|
addAnswer(rr: RecourceRecord) {
|
||||||
public _additionals: RecourceRecord[] = [];
|
this._answers.push(rr.lock())
|
||||||
|
}
|
||||||
|
|
||||||
|
private _authorities: Serializer[] = [];
|
||||||
|
addAuthorities(rr: RecourceRecord) {
|
||||||
|
this._authorities.push(rr.lock())
|
||||||
|
}
|
||||||
|
|
||||||
|
private _additionals: Serializer[] = [];
|
||||||
|
addAdditionals(rr: RecourceRecord) {
|
||||||
|
this._additionals.push(rr.lock())
|
||||||
|
}
|
||||||
|
|
||||||
constructor(packet: Buffer, public sender: string, private max_size = 0) {
|
constructor(packet: Buffer, public sender: string, private max_size = 0) {
|
||||||
let parsed = parseInput(packet);
|
let parsed = parseInput(packet);
|
||||||
@ -103,9 +114,9 @@ export class Request {
|
|||||||
|
|
||||||
let header = this._header.lock();
|
let header = this._header.lock();
|
||||||
let questions = this._questions.map(e => e.lock())
|
let questions = this._questions.map(e => e.lock())
|
||||||
let answers = this._answers.map(e => e.lock())
|
let answers = this._answers;
|
||||||
let authority = this._authorities.map(e => e.lock())
|
let authority = this._authorities;
|
||||||
let additional = this._additionals.map(e => e.lock())
|
let additional = this._additionals;
|
||||||
|
|
||||||
let length = header.length;
|
let length = header.length;
|
||||||
questions.forEach(e => length += e.length);
|
questions.forEach(e => length += e.length);
|
||||||
|
185
src/test.ts
185
src/test.ts
@ -2,7 +2,7 @@ import { assert } from "chai";
|
|||||||
|
|
||||||
import { parseInput, Request, questionParser, headerParser } from "./request"
|
import { parseInput, Request, questionParser, headerParser } from "./request"
|
||||||
import { IMessageHeader, RecordTypes } from "./types";
|
import { IMessageHeader, RecordTypes } from "./types";
|
||||||
import { RDATARecord } from "./record";
|
import { RDATARecord, RecourceRecord } from "./record";
|
||||||
import { Question } from "./question";
|
import { Question } from "./question";
|
||||||
import { Header } from "./header";
|
import { Header } from "./header";
|
||||||
|
|
||||||
@ -332,8 +332,187 @@ describe("parser", function () {
|
|||||||
rr.TTL = 1600
|
rr.TTL = 1600
|
||||||
rr.TYPE = 1
|
rr.TYPE = 1
|
||||||
rr.RDATA = fromHex("0A 00 00 01")
|
rr.RDATA = fromHex("0A 00 00 01")
|
||||||
request._answers.push(rr)
|
request.addAnswer(rr);
|
||||||
let data = request.serialize()
|
let data = request.serialize()
|
||||||
assert.equal(data.toString("hex"), should.replace(/\s/g, "").toLowerCase(), "Whole packet serialization failed")
|
assert.equal(data.toString("hex"), should.replace(/\s/g, "").toLowerCase(), "Whole packet serialization failed")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
import { DnsCore, StoragePlugin, Record, MonitoringPlugin, QuestionPlugin, AnswerHandler, ListenerPlugin } from "./index"
|
||||||
|
|
||||||
|
describe("DNS Core", function () {
|
||||||
|
it("Initialization", () => {
|
||||||
|
let core = new DnsCore();
|
||||||
|
assert.exists(core, "Core constructor working")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Adding fake plugins and initialize", () => {
|
||||||
|
let core = new DnsCore();
|
||||||
|
|
||||||
|
core.addListener({
|
||||||
|
init: async (c) => { assert.exists(c, "Core not defined on Plugin init") },
|
||||||
|
Priority: 0,
|
||||||
|
registerCallback: () => { }
|
||||||
|
})
|
||||||
|
|
||||||
|
core.addMonitoring({
|
||||||
|
init: async (c) => { assert.exists(c, "Core not defined on Plugin init") },
|
||||||
|
Priority: 0,
|
||||||
|
onRequest: () => { }
|
||||||
|
})
|
||||||
|
|
||||||
|
core.addQuestion({
|
||||||
|
init: async (c) => { assert.exists(c, "Core not defined on Plugin init") },
|
||||||
|
QuestionTypes: [RecordTypes.A],
|
||||||
|
Priority: 0,
|
||||||
|
handleQuestion: async () => { },
|
||||||
|
})
|
||||||
|
|
||||||
|
core.addStorage({
|
||||||
|
getAllRecordsForDomain: async () => undefined,
|
||||||
|
getRecords: async () => undefined,
|
||||||
|
init: async (c) => { assert.exists(c, "Core not defined on Plugin init") },
|
||||||
|
isResponsible: () => true,
|
||||||
|
NoCache: false,
|
||||||
|
Priority: 0,
|
||||||
|
RecordTypes: [RecordTypes.A]
|
||||||
|
})
|
||||||
|
|
||||||
|
return core.start();
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Test Request flow", async function () {
|
||||||
|
let core = new DnsCore();
|
||||||
|
|
||||||
|
let listener = new TestListenerPlugin();
|
||||||
|
core.addListener(listener)
|
||||||
|
|
||||||
|
let monitoring = new TestMonitoringPlugin({
|
||||||
|
domain: "example.com",
|
||||||
|
hostname: "test",
|
||||||
|
type: "A"
|
||||||
|
});
|
||||||
|
core.addMonitoring(monitoring)
|
||||||
|
|
||||||
|
|
||||||
|
let question = new TestQuestionPlugin();
|
||||||
|
core.addQuestion(question)
|
||||||
|
|
||||||
|
let storage = new TestStoragePlugin({
|
||||||
|
domain: "example.com",
|
||||||
|
hostname: "test",
|
||||||
|
type: RecordTypes.A,
|
||||||
|
ttl: 500,
|
||||||
|
value: "10.0.0.1"
|
||||||
|
})
|
||||||
|
core.addStorage(storage);
|
||||||
|
|
||||||
|
await core.start();
|
||||||
|
|
||||||
|
let reqData = fromHex("E835 0100 0001 0000 0000 0000 04 74657374 07 6578616D706c65 03 636F6D 00 0001 0001");
|
||||||
|
let should = "E835 8580 0001000100000000 04 74657374 076578616D706C6503636F6D0000010001 04 74657374 07 6578616D706C65 03 636F6D 00 0001 0001 0000 0640 0004 0A000001"
|
||||||
|
let resData = await listener.sendRequest(reqData)
|
||||||
|
|
||||||
|
assert.equal(resData.toString("hex"), should.replace(/\s/g, "").toLowerCase(), "Whole packet serialization failed")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
class TestStoragePlugin implements StoragePlugin {
|
||||||
|
re: Record = { domain: "example.com", hostname: "test", type: RecordTypes.A, value: "10.0.0.1", ttl: 1600 }
|
||||||
|
|
||||||
|
constructor(record?: Record) {
|
||||||
|
if (record)
|
||||||
|
this.re = record;
|
||||||
|
}
|
||||||
|
|
||||||
|
Priority = 0
|
||||||
|
NoCache = false
|
||||||
|
RecordTypes = [RecordTypes.A]
|
||||||
|
isResponsible(domain: string) {
|
||||||
|
// console.log("Is responsible")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(core) { assert.exists(core, "TestStoragePlugin init no core object") }
|
||||||
|
|
||||||
|
async getRecords(domain, hostname, type) {
|
||||||
|
if (domain !== this.re.domain || hostname !== this.re.hostname || type != this.re.type) return undefined;
|
||||||
|
return [this.re];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllRecordsForDomain(domain) {
|
||||||
|
if (domain !== this.re.domain) return undefined;
|
||||||
|
return [this.re];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestMonitoringPlugin implements MonitoringPlugin {
|
||||||
|
constructor(private should: { domain: string, hostname: string, type: string }) { }
|
||||||
|
|
||||||
|
async init(core) { assert.exists(core, "TestMonitoringPlugin init no core object") }
|
||||||
|
Priority = 0
|
||||||
|
onRequest(domain, hostname, type) {
|
||||||
|
assert.equal(this.should.domain, domain, "Domain value wrong")
|
||||||
|
assert.equal(this.should.hostname, hostname, "Domain value wrong")
|
||||||
|
assert.equal(this.should.type, type, "Domain value wrong")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ARR extends RecourceRecord {
|
||||||
|
TYPE = RecordTypes.A
|
||||||
|
|
||||||
|
constructor(domain: string, ip: string) {
|
||||||
|
super();
|
||||||
|
this.NAME = domain
|
||||||
|
this.TTL = 1600
|
||||||
|
let data = Buffer.alloc(4)
|
||||||
|
let idx = 0;
|
||||||
|
ip.split(".").forEach(e => {
|
||||||
|
data.writeUInt8(Number(e), idx);
|
||||||
|
idx++;
|
||||||
|
})
|
||||||
|
this.dataLock = () => {
|
||||||
|
return {
|
||||||
|
length: 4,
|
||||||
|
serialize: (buffer, offset) => {
|
||||||
|
data.copy(buffer, offset);
|
||||||
|
return offset + 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// this.RDATA = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestQuestionPlugin implements QuestionPlugin {
|
||||||
|
private Core: DnsCore
|
||||||
|
QuestionTypes = [RecordTypes.A]
|
||||||
|
Priority = 0
|
||||||
|
async init(core) {
|
||||||
|
assert.exists(core, "TestQuestionPlugin init no core object")
|
||||||
|
this.Core = core;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleQuestion(question: Question, request: AnswerHandler, next: () => void) {
|
||||||
|
assert(this.QuestionTypes.find(e => e === <any>question.QTYPE), "Handler was called with not supported questin type")
|
||||||
|
let parts = question.QNAME.split(".")
|
||||||
|
let domain = parts.splice(-2, 2).join(".")
|
||||||
|
let hostname = parts.join(".");
|
||||||
|
hostname = hostname !== "" ? hostname : "*"
|
||||||
|
let records = await this.Core.storageManager.getRecords(domain, hostname, question.QTYPE)
|
||||||
|
records.forEach(e => request.addAnswer(new ARR(question.QNAME, e.value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestListenerPlugin implements ListenerPlugin {
|
||||||
|
async init(core) { assert.exists(core, "TestListenerPlugin init no core object") }
|
||||||
|
Priority = 0;
|
||||||
|
callback: (data: Buffer, sender: string, max_size?: number) => Promise<Buffer>;
|
||||||
|
registerCallback(callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRequest(message: Buffer) {
|
||||||
|
return this.callback(message, "localhost")
|
||||||
|
}
|
||||||
|
}
|
@ -305,10 +305,3 @@ export interface MessageRecourceRecord {
|
|||||||
*/
|
*/
|
||||||
RDATA: Buffer;
|
RDATA: Buffer;
|
||||||
}
|
}
|
||||||
// export interface IMessage {
|
|
||||||
// header: IMessageHeader;
|
|
||||||
// questions: IMessageQuestion[];
|
|
||||||
// _answers: MessageRecourceRecord[];
|
|
||||||
// _authorities: MessageRecourceRecord[];
|
|
||||||
// _additionals: MessageRecourceRecord[];
|
|
||||||
// }
|
|
||||||
|
Loading…
Reference in New Issue
Block a user