nodename-core/src/test.ts
Fabian Stamm 07e2dff29f
All checks were successful
the build was successful
Optimizing export
2018-12-03 07:59:29 +01:00

518 lines
17 KiB
TypeScript

import { assert } from "chai";
import { parseInput, Request, questionParser, headerParser } from "./request"
import { IMessageHeader, RecordTypes } from "./types";
import { RDATARecord, RecourceRecord } from "./record";
import { Question } from "./question";
import { Header } from "./header";
function fromHex(data: string) {
return Buffer.from(data.replace(/\s/g, ""), "hex");
}
describe("parser", function () {
describe("header", function () {
describe("header parser", function () {
let should_templ: IMessageHeader = {
ID: 0, QR: 0, OPCODE: 0, AA: 0, TC: 0, RD: 0, RA: 0, Z: 0, AD: 0, CD: 0, RCODE: 0, QDCOUNT: 0, ANCOUNT: 0, ARCOUNT: 0, NSCOUNT: 0
}
let tests: { name: string, data: string, fields: Partial<IMessageHeader> }[] = [
{
name: "Testing ID field",
data: "0001 0000 0000 0000 0000 0000",
fields: {
ID: 1
}
},
{
name: "Testing ID field with max value",
data: "FFFF 0000 0000 0000 0000 0000",
fields: {
ID: 65535
}
},
{
name: "Testing QR field",
data: "0000 8000 0000 0000 0000 0000",
fields: {
QR: 1
}
},
{
name: "Testing OPCODE field value 2",
data: "0000 1000 0000 0000 0000 0000",
fields: {
OPCODE: 2
}
},
{
name: "Testing OPCODE field value 1",
data: "0000 0800 0000 0000 0000 0000",
fields: {
OPCODE: 1
}
},
{
name: "Testing AA field",
data: "0000 0400 0000 0000 0000 0000",
fields: {
AA: 1
}
},
{
name: "Testing TC field",
data: "0000 0200 0000 0000 0000 0000",
fields: {
TC: 1
}
},
{
name: "Testing RD field",
data: "0000 0100 0000 0000 0000 0000",
fields: {
RD: 1
}
},
{
name: "Testing RCODE field",
data: "0000 0002 0000 0000 0000 0000",
fields: {
RCODE: 2
}
},
{
name: "Testing QDCOUNT field max value",
data: "0000 0000 FFFF 0000 0000 0000",
fields: {
QDCOUNT: 65535
}
},
{
name: "Testing ANCOUNT field max value",
data: "0000 0000 0000 FFFF 0000 0000",
fields: {
ANCOUNT: 65535
}
},
{
name: "Testing NSCOUNT field max value",
data: "0000 0000 0000 0000 FFFF 0000",
fields: {
NSCOUNT: 65535
}
},
{
name: "Testing ARCOUNT field max value",
data: "0000 0000 0000 0000 0000 FFFF",
fields: {
ARCOUNT: 65535
}
},
{
name: "Testing all Flags and Values max",
data: "FFFF FFFF FFFF FFFF FFFF FFFF",
fields: {
ID: 65535, QR: 1, OPCODE: 15, AA: 1, TC: 1, RD: 1, RA: 1, Z: 1, AD: 1, CD: 1, RCODE: 15, QDCOUNT: 65535, ANCOUNT: 65535, NSCOUNT: 65535, ARCOUNT: 65535
}
}
]
tests.forEach(function (e) {
it(e.name, function () {
let testdata = fromHex(e.data)
let should: IMessageHeader = { ...should_templ, ...e.fields }
let header = <any>headerParser.parse(testdata);
assert.hasAllKeys(header, Object.keys(should), "Parsed header is missing some fields")
assert.deepEqual(header, should, "Parsed header has not expected values!")
})
})
})
describe("header serializer", function () {
let empty_header: IMessageHeader = {
ID: 0, QR: 0, OPCODE: 0, AA: 0, TC: 0, RD: 0, RA: 0, Z: 0, AD: 0, CD: 0, RCODE: 0, QDCOUNT: 0, ANCOUNT: 0, ARCOUNT: 0, NSCOUNT: 0
}
let tests: { name: string, result: string, values: Partial<IMessageHeader> }[] = [
{
name: "Fill header with 0s",
result: "0000 0000 0000 0000 0000 0000",
values: {}
},
{
name: "Set header id to 5",
result: "0005 0000 0000 0000 0000 0000",
values: {
ID: 5
}
},
{
name: "Set QR",
result: "0000 8000 0000 0000 0000 0000",
values: {
QR: 1
}
},
{
name: "Set OPCODE",
result: "0000 7800 0000 0000 0000 0000",
values: {
OPCODE: 15
}
},
{
name: "Set AA",
result: "0000 0400 0000 0000 0000 0000",
values: {
AA: 1
}
},
{
name: "Set TC",
result: "0000 0200 0000 0000 0000 0000",
values: {
TC: 1
}
},
{
name: "Set RD",
result: "0000 0100 0000 0000 0000 0000",
values: {
RD: 1
}
},
{
name: "Set RA",
result: "0000 0080 0000 0000 0000 0000",
values: {
RA: 1
}
},
{
name: "Set Z",
result: "0000 0040 0000 0000 0000 0000",
values: {
Z: 1
}
},
{
name: "Set AD",
result: "0000 0020 0000 0000 0000 0000",
values: {
AD: 1
}
},
{
name: "Set CD",
result: "0000 0010 0000 0000 0000 0000",
values: {
CD: 1
}
},
{
name: "Set RCODE",
result: "0000 000F 0000 0000 0000 0000",
values: {
RCODE: 15
}
},
{
name: "Set QDCOUNT",
result: "0000 0000 FFFF 0000 0000 0000",
values: {
QDCOUNT: 65535
}
},
{
name: "Set ANCOUNT",
result: "0000 0000 0000 FFFF 0000 0000",
values: {
ANCOUNT: 65535
}
},
{
name: "Set NSCOUNT",
result: "0000 0000 0000 0000 FFFF 0000",
values: {
NSCOUNT: 65535
}
},
{
name: "Set ARCOUNT",
result: "0000 0000 0000 0000 0000 FFFF",
values: {
ARCOUNT: 65535
}
},
]
tests.forEach(function (e) {
it(e.name, function () {
let header = Object.assign({}, empty_header, e.values)
let slz = new Header(header).lock();
assert.equal(slz.length, 12, "Header returns wrong length");
let res = Buffer.alloc(slz.length);
assert.equal(slz.serialize(res, 0), 12, "Header returns wrong offset");
assert.equal(res.toString("hex"), e.result.replace(/\s/g, "").toLowerCase(), "Header serialization failed");
})
})
})
})
describe("question", function () {
let questionData = fromHex("0474 6573 7407 6578 616d 706c 6503 636f 6d00 0001 0001")
let questionObj = {
QNAME: "test.example.com",
QTYPE: RecordTypes.A,
QCLASS: 1
}
it("check question parser with one question", function () {
let res = questionParser.parse(questionData)
assert.deepEqual(res, questionObj, "Question parser does not parse input correctly")
})
it("check question serialization", function () {
let slz = new Question(questionObj).lock()
assert.equal(slz.length, 22, "Question serializer returns wrong length")
let res = Buffer.alloc(slz.length);
assert.equal(slz.serialize(res, 0), 22, "Question serializer returns wrong offset")
assert.equal(res.toString("hex"), questionData.toString("hex"), "Query serializer does not serialite correctly");
})
})
it("parse valid request", () => {
let should = {
header: { ID: 59445, QR: 0, OPCODE: 0, AA: 0, TC: 0, RD: 1, RA: 0, Z: 0, AD: 0, CD: 0, RCODE: 0, QDCOUNT: 1, ANCOUNT: 0, NSCOUNT: 0, ARCOUNT: 0 },
questions: [{ QNAME: 'example.com', QTYPE: 1, QCLASS: 1 }]
}
let data = fromHex("E835 0100 0001 0000 0000 0000 07 6578616D706c65 03636F6D 00 0001 0001");
let res = parseInput(data);
assert.deepEqual(res, should);
})
it("recource record serialization", function () {
let should = "07 6578616D706C65 03 636f6D 00 0001 0001 00000640 0004 0A000001"
let rr = new RDATARecord()
rr.CLASS = 1
rr.NAME = "example.com"
rr.TTL = 1600
rr.TYPE = 1
rr.RDATA = fromHex("0A 00 00 01")
let srl = rr.lock();
let res = Buffer.alloc(srl.length);
srl.serialize(res, 0);
assert.equal(res.toString("hex"), should.replace(/\s/g, "").toLowerCase(), "Serialization not working properly")
})
it("recource record constructor value assign", function () {
let should = "07 6578616D706C65 03 636f6D 00 0001 0001 00000640 0004 0A000001"
let rr = new RDATARecord({
CLASS: 1,
NAME: "example.com",
TTL: 1600,
TYPE: 1,
RDATA: fromHex("0A 00 00 01")
})
let srl = rr.lock();
assert.equal(srl.length, 27, "Record serializer returns wrong length")
let res = Buffer.alloc(srl.length);
assert.equal(srl.serialize(res, 0), 27, "Record serializer returns wrong offset")
assert.equal(res.toString("hex"), should.replace(/\s/g, "").toLowerCase(), "Record serialization not working properly")
})
it("full response serialization", function () {
let reqData = fromHex("E835 0100 0001 0000 0000 0000 07 6578616D706c65 03636F6D 00 0001 0001");
let should = "E835 8580 0001000100000000 076578616D706C6503636F6D0000010001 07 6578616D706C65 03 636F6D 00 0001 0001 0000 0640 0004 0A000001"
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);
let data = request.serialize()
assert.equal(data.toString("hex"), should.replace(/\s/g, "").toLowerCase(), "Whole packet serialization failed")
})
})
import { DnsCore, StoragePlugin, Record, MonitoringPlugin, QuestionPlugin, AnswerHandler, ListenerPlugin } from "./core"
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")
}
}