Refactoring and adding Question class
This commit is contained in:
parent
a34cd4bac2
commit
4e3cf05d29
137
lib/request.d.ts
vendored
137
lib/request.d.ts
vendored
@ -1,113 +1,49 @@
|
|||||||
/// <reference types="node" />
|
/// <reference types="node" />
|
||||||
import { Message, MessageHeader, MessageQuestion, MessageRecourceRecord, ErrorCodes } from "./types";
|
import { IMessage, IMessageHeader, IMessageQuestion, MessageRecourceRecord, ErrorCodes } from "./types";
|
||||||
export declare enum QueryTypes {
|
export declare function parseHeader(data: Buffer): IMessageHeader;
|
||||||
/**
|
export declare function parseQuestions(count: number, packet: Buffer): IMessageQuestion[];
|
||||||
* IPv4 address
|
export declare function serializeName(name: string): Buffer;
|
||||||
*/
|
export declare class Request implements IMessage {
|
||||||
A = 1,
|
|
||||||
/**
|
|
||||||
* Nameserver
|
|
||||||
*/
|
|
||||||
NS = 2,
|
|
||||||
/**
|
|
||||||
* Obsolete
|
|
||||||
*/
|
|
||||||
MD = 3,
|
|
||||||
/**
|
|
||||||
* Obsolete
|
|
||||||
*/
|
|
||||||
MF = 4,
|
|
||||||
/**
|
|
||||||
* Alias
|
|
||||||
*/
|
|
||||||
CNAME = 5,
|
|
||||||
/**
|
|
||||||
* Start of authority
|
|
||||||
*/
|
|
||||||
SOA = 6,
|
|
||||||
/**
|
|
||||||
* Experimental
|
|
||||||
*/
|
|
||||||
MB = 7,
|
|
||||||
/**
|
|
||||||
* Experimental
|
|
||||||
*/
|
|
||||||
MG = 8,
|
|
||||||
/**
|
|
||||||
* Experimental
|
|
||||||
*/
|
|
||||||
MR = 9,
|
|
||||||
/**
|
|
||||||
* Experimental
|
|
||||||
*/
|
|
||||||
NULL = 10,
|
|
||||||
/**
|
|
||||||
* Service description
|
|
||||||
*/
|
|
||||||
WKS = 11,
|
|
||||||
/**
|
|
||||||
* Reverse entry (inaddr.arpa)
|
|
||||||
*/
|
|
||||||
PTR = 12,
|
|
||||||
/**
|
|
||||||
* Host information
|
|
||||||
*/
|
|
||||||
HINFO = 13,
|
|
||||||
/**
|
|
||||||
* Mailbox / Mail-list information
|
|
||||||
*/
|
|
||||||
MINFO = 14,
|
|
||||||
/**
|
|
||||||
* Mail exchange
|
|
||||||
*/
|
|
||||||
MX = 15,
|
|
||||||
/**
|
|
||||||
* Text strings
|
|
||||||
*/
|
|
||||||
TXT = 16,
|
|
||||||
/**
|
|
||||||
* IPv6 address
|
|
||||||
*/
|
|
||||||
AAAA = 28,
|
|
||||||
/**
|
|
||||||
* SRV records
|
|
||||||
*/
|
|
||||||
SRV = 33,
|
|
||||||
/**
|
|
||||||
* Request to transfer entire zone
|
|
||||||
*/
|
|
||||||
AXFR = 252,
|
|
||||||
/**
|
|
||||||
* Request for mailbox related records
|
|
||||||
*/
|
|
||||||
MAILA = 254,
|
|
||||||
/**
|
|
||||||
* Request for mail agend RRs
|
|
||||||
*/
|
|
||||||
MAILB = 253,
|
|
||||||
/**
|
|
||||||
* Any class
|
|
||||||
*/
|
|
||||||
ANY = 255,
|
|
||||||
}
|
|
||||||
export declare function SerializeName(name: string): Buffer;
|
|
||||||
export declare class Request implements Message {
|
|
||||||
private sendCallback;
|
private sendCallback;
|
||||||
|
private max_size;
|
||||||
private _header;
|
private _header;
|
||||||
readonly header: MessageHeader;
|
readonly header: Header;
|
||||||
private _questions;
|
private _questions;
|
||||||
readonly questions: MessageQuestion[];
|
readonly questions: Question[];
|
||||||
answers: RecourceRecord[];
|
answers: RecourceRecord[];
|
||||||
authorities: RecourceRecord[];
|
authorities: RecourceRecord[];
|
||||||
additionals: RecourceRecord[];
|
additionals: RecourceRecord[];
|
||||||
constructor(packet: Buffer, sendCallback: (packet: Buffer) => any);
|
constructor(packet: Buffer, sendCallback: (packet: Buffer) => any, max_size?: number);
|
||||||
error(error: ErrorCodes): void;
|
error(error: ErrorCodes): void;
|
||||||
noRecursion(): void;
|
noRecursion(): void;
|
||||||
send(): void;
|
send(): void;
|
||||||
serialize(truncate?: boolean, rcode?: 0 | 1 | 2 | 3 | 4 | 5): Buffer;
|
serialize(): Buffer;
|
||||||
private serializeHeader();
|
}
|
||||||
private serializeQuestion(question);
|
export declare class Header implements IMessageHeader {
|
||||||
private serializeResourceRecord(record);
|
ID: number;
|
||||||
|
QR: 0 | 1;
|
||||||
|
OPCODE: number;
|
||||||
|
AA: 0 | 1;
|
||||||
|
TC: 0 | 1;
|
||||||
|
RD: 0 | 1;
|
||||||
|
RA: 0 | 1;
|
||||||
|
Z: 0 | 1;
|
||||||
|
AD: 0 | 1;
|
||||||
|
CD: 0 | 1;
|
||||||
|
RCODE: ErrorCodes;
|
||||||
|
QDCOUNT: number;
|
||||||
|
ANCOUNT: number;
|
||||||
|
NSCOUNT: number;
|
||||||
|
ARCOUNT: number;
|
||||||
|
constructor(header: IMessageHeader);
|
||||||
|
serialize(): Buffer;
|
||||||
|
}
|
||||||
|
export declare class Question implements IMessageQuestion {
|
||||||
|
QNAME: string;
|
||||||
|
QTYPE: number;
|
||||||
|
QCLASS: number;
|
||||||
|
constructor(question: IMessageQuestion);
|
||||||
|
serialize(): Buffer;
|
||||||
}
|
}
|
||||||
export declare class RecourceRecord implements MessageRecourceRecord {
|
export declare class RecourceRecord implements MessageRecourceRecord {
|
||||||
/**
|
/**
|
||||||
@ -123,4 +59,5 @@ export declare class RecourceRecord implements MessageRecourceRecord {
|
|||||||
TTL: number;
|
TTL: number;
|
||||||
RDATA: Buffer;
|
RDATA: Buffer;
|
||||||
readonly RDLENGTH: number;
|
readonly RDLENGTH: number;
|
||||||
|
serialize(): Buffer;
|
||||||
}
|
}
|
||||||
|
271
lib/request.js
271
lib/request.js
@ -2,98 +2,6 @@
|
|||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const binary_parser_1 = require("binary-parser");
|
const binary_parser_1 = require("binary-parser");
|
||||||
const types_1 = require("./types");
|
const types_1 = require("./types");
|
||||||
const MAX_LABEL_SIZE = 63;
|
|
||||||
var QueryTypes;
|
|
||||||
(function (QueryTypes) {
|
|
||||||
/**
|
|
||||||
* IPv4 address
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["A"] = 1] = "A";
|
|
||||||
/**
|
|
||||||
* Nameserver
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["NS"] = 2] = "NS";
|
|
||||||
/**
|
|
||||||
* Obsolete
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["MD"] = 3] = "MD";
|
|
||||||
/**
|
|
||||||
* Obsolete
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["MF"] = 4] = "MF";
|
|
||||||
/**
|
|
||||||
* Alias
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["CNAME"] = 5] = "CNAME";
|
|
||||||
/**
|
|
||||||
* Start of authority
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["SOA"] = 6] = "SOA";
|
|
||||||
/**
|
|
||||||
* Experimental
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["MB"] = 7] = "MB";
|
|
||||||
/**
|
|
||||||
* Experimental
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["MG"] = 8] = "MG";
|
|
||||||
/**
|
|
||||||
* Experimental
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["MR"] = 9] = "MR";
|
|
||||||
/**
|
|
||||||
* Experimental
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["NULL"] = 10] = "NULL";
|
|
||||||
/**
|
|
||||||
* Service description
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["WKS"] = 11] = "WKS";
|
|
||||||
/**
|
|
||||||
* Reverse entry (inaddr.arpa)
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["PTR"] = 12] = "PTR";
|
|
||||||
/**
|
|
||||||
* Host information
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["HINFO"] = 13] = "HINFO";
|
|
||||||
/**
|
|
||||||
* Mailbox / Mail-list information
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["MINFO"] = 14] = "MINFO";
|
|
||||||
/**
|
|
||||||
* Mail exchange
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["MX"] = 15] = "MX";
|
|
||||||
/**
|
|
||||||
* Text strings
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["TXT"] = 16] = "TXT";
|
|
||||||
/**
|
|
||||||
* IPv6 address
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["AAAA"] = 28] = "AAAA";
|
|
||||||
/**
|
|
||||||
* SRV records
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["SRV"] = 33] = "SRV";
|
|
||||||
/**
|
|
||||||
* Request to transfer entire zone
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["AXFR"] = 252] = "AXFR";
|
|
||||||
/**
|
|
||||||
* Request for mailbox related records
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["MAILA"] = 254] = "MAILA";
|
|
||||||
/**
|
|
||||||
* Request for mail agend RRs
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["MAILB"] = 253] = "MAILB";
|
|
||||||
/**
|
|
||||||
* Any class
|
|
||||||
*/
|
|
||||||
QueryTypes[QueryTypes["ANY"] = 255] = "ANY";
|
|
||||||
})(QueryTypes = exports.QueryTypes || (exports.QueryTypes = {}));
|
|
||||||
const headerParser = new binary_parser_1.Parser()
|
const headerParser = new binary_parser_1.Parser()
|
||||||
.endianess("big")
|
.endianess("big")
|
||||||
.uint16("ID")
|
.uint16("ID")
|
||||||
@ -111,6 +19,15 @@ const headerParser = new binary_parser_1.Parser()
|
|||||||
.uint16("ANCOUNT")
|
.uint16("ANCOUNT")
|
||||||
.uint16("NSCOUNT")
|
.uint16("NSCOUNT")
|
||||||
.uint16("ARCOUNT");
|
.uint16("ARCOUNT");
|
||||||
|
function parseHeader(data) {
|
||||||
|
try {
|
||||||
|
return headerParser.parse(data);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw new Error("Header parsing failed" + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.parseHeader = parseHeader;
|
||||||
const labelParser = new binary_parser_1.Parser()
|
const labelParser = new binary_parser_1.Parser()
|
||||||
.endianess("big")
|
.endianess("big")
|
||||||
.uint8("dataLength")
|
.uint8("dataLength")
|
||||||
@ -133,14 +50,21 @@ const questionParser = new binary_parser_1.Parser()
|
|||||||
.uint16("QTYPE")
|
.uint16("QTYPE")
|
||||||
.uint16("QCLASS");
|
.uint16("QCLASS");
|
||||||
function parseQuestions(count, packet) {
|
function parseQuestions(count, packet) {
|
||||||
|
try {
|
||||||
return new binary_parser_1.Parser()
|
return new binary_parser_1.Parser()
|
||||||
.endianess("big")
|
.endianess("big")
|
||||||
.array("questions", {
|
.array("questions", {
|
||||||
type: questionParser,
|
type: questionParser,
|
||||||
length: count
|
length: count
|
||||||
}).parse(packet).questions;
|
}).parse(packet).questions;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw new Error("Question parsing failed" + e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function SerializeName(name) {
|
exports.parseQuestions = parseQuestions;
|
||||||
|
const MAX_LABEL_SIZE = 63;
|
||||||
|
function serializeName(name) {
|
||||||
let length = 0;
|
let length = 0;
|
||||||
let parts = name.split(".");
|
let parts = name.split(".");
|
||||||
parts.forEach(e => {
|
parts.forEach(e => {
|
||||||
@ -161,10 +85,11 @@ function SerializeName(name) {
|
|||||||
data.writeUInt8(0, offset);
|
data.writeUInt8(0, offset);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
exports.SerializeName = SerializeName;
|
exports.serializeName = serializeName;
|
||||||
class Request {
|
class Request {
|
||||||
constructor(packet, sendCallback) {
|
constructor(packet, sendCallback, max_size = 512) {
|
||||||
this.sendCallback = sendCallback;
|
this.sendCallback = sendCallback;
|
||||||
|
this.max_size = max_size;
|
||||||
this.answers = [];
|
this.answers = [];
|
||||||
this.authorities = [];
|
this.authorities = [];
|
||||||
this.additionals = [];
|
this.additionals = [];
|
||||||
@ -172,17 +97,17 @@ class Request {
|
|||||||
packet.copy(headerData, 0, 0, 12);
|
packet.copy(headerData, 0, 0, 12);
|
||||||
let bodyData = Buffer.alloc(packet.length - 12);
|
let bodyData = Buffer.alloc(packet.length - 12);
|
||||||
packet.copy(bodyData, 0, 12, packet.length);
|
packet.copy(bodyData, 0, 12, packet.length);
|
||||||
this._header = headerParser.parse(headerData);
|
this._header = new Header(parseHeader(headerData));
|
||||||
this._header.AD = 0;
|
this._header.AD = 0;
|
||||||
this._header.RCODE = types_1.ErrorCodes.NoError;
|
this._header.RCODE = types_1.ErrorCodes.NoError;
|
||||||
this._header.RA = this._header.RD;
|
this._header.RA = this._header.RD;
|
||||||
this._questions = parseQuestions(this._header.QDCOUNT, bodyData);
|
this._questions = parseQuestions(this._header.QDCOUNT, bodyData).map(e => new Question(e));
|
||||||
}
|
}
|
||||||
get header() {
|
get header() {
|
||||||
return Object.assign({}, this._header);
|
return Object.assign({}, this._header);
|
||||||
}
|
}
|
||||||
get questions() {
|
get questions() {
|
||||||
return this._questions.map(e => e);
|
return this._questions.map(e => Object.assign({}, e));
|
||||||
}
|
}
|
||||||
error(error) {
|
error(error) {
|
||||||
if (this._header.RCODE === types_1.ErrorCodes.NoError)
|
if (this._header.RCODE === types_1.ErrorCodes.NoError)
|
||||||
@ -194,37 +119,43 @@ class Request {
|
|||||||
send() {
|
send() {
|
||||||
this.sendCallback(this.serialize());
|
this.sendCallback(this.serialize());
|
||||||
}
|
}
|
||||||
serialize(truncate = false, rcode = 0) {
|
serialize() {
|
||||||
this._header.AA = 1;
|
this._header.AA = 1;
|
||||||
this._header.ANCOUNT = this.answers.length;
|
this._header.ANCOUNT = this.answers.length;
|
||||||
this._header.ARCOUNT = this.additionals.length;
|
this._header.ARCOUNT = this.additionals.length;
|
||||||
this._header.NSCOUNT = this.authorities.length;
|
this._header.NSCOUNT = this.authorities.length;
|
||||||
this._header.QR = 1;
|
this._header.QR = 1;
|
||||||
this._header.RCODE = rcode;
|
let questions = this._questions.map(e => e.serialize());
|
||||||
let questions = this.questions.map(this.serializeQuestion, this);
|
let answers = this.answers.map(e => e.serialize());
|
||||||
let answers = this.answers.map(this.serializeResourceRecord, this);
|
let authority = this.authorities.map(e => e.serialize());
|
||||||
let authority = this.authorities.map(this.serializeResourceRecord, this);
|
let additional = this.additionals.map(e => e.serialize());
|
||||||
let additional = this.additionals.map(this.serializeResourceRecord, this);
|
let length = 12;
|
||||||
let questionsByteLength = 0;
|
questions.forEach(e => length += e.length);
|
||||||
questions.forEach(e => questionsByteLength += e.length);
|
answers.forEach(e => length += e.length);
|
||||||
let answersByteLength = 0;
|
authority.forEach(e => length += e.length);
|
||||||
answers.forEach(e => answersByteLength += e.length);
|
additional.forEach(e => length += e.length);
|
||||||
let authorityByteLength = 0;
|
// let questionsByteLength = 0;
|
||||||
authority.forEach(e => authorityByteLength += e.length);
|
// questions.forEach(e => questionsByteLength += e.length);
|
||||||
let additionalByteLength = 0;
|
// let answersByteLength = 0;
|
||||||
additional.forEach(e => additionalByteLength += e.length);
|
// answers.forEach(e => answersByteLength += e.length)
|
||||||
let length = 12 + questionsByteLength + answersByteLength + authorityByteLength + additionalByteLength; //Header is always 12 byte large
|
// let authorityByteLength = 0;
|
||||||
if (truncate && length > 512) {
|
// authority.forEach(e => authorityByteLength += e.length)
|
||||||
|
// let additionalByteLength = 0;
|
||||||
|
// additional.forEach(e => additionalByteLength += e.length)
|
||||||
|
// let length = 12 + questionsByteLength + answersByteLength + authorityByteLength + additionalByteLength; //Header is always 12 byte large
|
||||||
|
if (length > this.max_size) {
|
||||||
this._header.TC = 1;
|
this._header.TC = 1;
|
||||||
//Buffer will ignore data that exeeds the max buffer length
|
//Will ignore data, that exceeds length
|
||||||
length = 512;
|
length = this.max_size;
|
||||||
}
|
}
|
||||||
let header = this.serializeHeader();
|
let header = this._header.serialize();
|
||||||
let data = Buffer.alloc(length);
|
let data = Buffer.alloc(length);
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
let append = (buffer) => {
|
let append = (buffer) => {
|
||||||
|
if (offset <= length) {
|
||||||
buffer.copy(data, offset, 0, buffer.length);
|
buffer.copy(data, offset, 0, buffer.length);
|
||||||
offset += buffer.length;
|
offset += buffer.length;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
append(header);
|
append(header);
|
||||||
questions.forEach(append);
|
questions.forEach(append);
|
||||||
@ -233,57 +164,55 @@ class Request {
|
|||||||
additional.forEach(append);
|
additional.forEach(append);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
serializeHeader() {
|
}
|
||||||
let header = this.header;
|
exports.Request = Request;
|
||||||
|
class Header {
|
||||||
|
constructor(header) {
|
||||||
|
for (let k in header) {
|
||||||
|
this[k] = header[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serialize() {
|
||||||
let data = Buffer.alloc(12);
|
let data = Buffer.alloc(12);
|
||||||
data.writeUInt16BE(header.ID, 0);
|
data.writeUInt16BE(this.ID, 0);
|
||||||
var f = 0x0000;
|
var f = 0x0000;
|
||||||
f = f | (header.QR << 15);
|
f = f | (this.QR << 15);
|
||||||
f = f | (header.OPCODE << 11);
|
f = f | (this.OPCODE << 11);
|
||||||
f = f | (header.AA << 10);
|
f = f | (this.AA << 10);
|
||||||
f = f | (header.TC << 9);
|
f = f | (this.TC << 9);
|
||||||
f = f | (header.RD << 8);
|
f = f | (this.RD << 8);
|
||||||
f = f | (header.RA << 7);
|
f = f | (this.RA << 7);
|
||||||
f = f | (header.Z << 6);
|
f = f | (this.Z << 6);
|
||||||
f = f | (header.AD << 5);
|
f = f | (this.AD << 5);
|
||||||
f = f | (header.CD << 4);
|
f = f | (this.CD << 4);
|
||||||
f = f | header.RCODE;
|
f = f | this.RCODE;
|
||||||
data.writeUInt16BE(f, 2);
|
data.writeUInt16BE(f, 2);
|
||||||
data.writeUInt16BE(header.QDCOUNT, 4);
|
data.writeUInt16BE(this.QDCOUNT, 4);
|
||||||
data.writeUInt16BE(header.ANCOUNT, 6);
|
data.writeUInt16BE(this.ANCOUNT, 6);
|
||||||
data.writeUInt16BE(header.NSCOUNT, 8);
|
data.writeUInt16BE(this.NSCOUNT, 8);
|
||||||
data.writeUInt16BE(header.ARCOUNT, 10);
|
data.writeUInt16BE(this.ARCOUNT, 10);
|
||||||
return data;
|
|
||||||
}
|
|
||||||
serializeQuestion(question) {
|
|
||||||
let qname = SerializeName(question.QNAME);
|
|
||||||
let data = Buffer.alloc(qname.length + 4);
|
|
||||||
qname.copy(data, 0, 0, qname.length);
|
|
||||||
let offset = qname.length;
|
|
||||||
data.writeUInt16BE(question.QTYPE, offset);
|
|
||||||
offset += 2;
|
|
||||||
data.writeUInt16BE(question.QCLASS, offset);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
serializeResourceRecord(record) {
|
|
||||||
// TODO: Implement compression
|
|
||||||
let name = SerializeName(record.NAME);
|
|
||||||
let data = Buffer.alloc(name.length + 10 + record.RDLENGTH); // For TYPE, CLASS, TTL, RLENGTH
|
|
||||||
name.copy(data, 0, 0, name.length);
|
|
||||||
let offset = name.length;
|
|
||||||
data.writeUInt16BE(record.TYPE, offset);
|
|
||||||
offset += 2;
|
|
||||||
data.writeUInt16BE(record.CLASS, offset);
|
|
||||||
offset += 2;
|
|
||||||
data.writeUInt32BE(record.TTL, offset);
|
|
||||||
offset += 4;
|
|
||||||
data.writeUInt16BE(record.RDLENGTH, offset);
|
|
||||||
offset += 2;
|
|
||||||
record.RDATA.copy(data, offset, 0, record.RDLENGTH);
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.Request = Request;
|
exports.Header = Header;
|
||||||
|
class Question {
|
||||||
|
constructor(question) {
|
||||||
|
for (let k in question) {
|
||||||
|
this[k] = question[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serialize() {
|
||||||
|
let qname = serializeName(this.QNAME);
|
||||||
|
let data = Buffer.alloc(qname.length + 4);
|
||||||
|
qname.copy(data, 0, 0, qname.length);
|
||||||
|
let offset = qname.length;
|
||||||
|
data.writeUInt16BE(this.QTYPE, offset);
|
||||||
|
offset += 2;
|
||||||
|
data.writeUInt16BE(this.QCLASS, offset);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Question = Question;
|
||||||
class RecourceRecord {
|
class RecourceRecord {
|
||||||
set TYPE(value) {
|
set TYPE(value) {
|
||||||
if (value < 0 || value > 65535)
|
if (value < 0 || value > 65535)
|
||||||
@ -304,6 +233,7 @@ class RecourceRecord {
|
|||||||
set TTL(value) {
|
set TTL(value) {
|
||||||
if (value < 0 || value > 4294967295)
|
if (value < 0 || value > 4294967295)
|
||||||
throw new TypeError("TTL Range: 0 - 4.294.967.295");
|
throw new TypeError("TTL Range: 0 - 4.294.967.295");
|
||||||
|
this._TTL = value;
|
||||||
}
|
}
|
||||||
get TTL() {
|
get TTL() {
|
||||||
return this._TTL;
|
return this._TTL;
|
||||||
@ -311,6 +241,23 @@ class RecourceRecord {
|
|||||||
get RDLENGTH() {
|
get RDLENGTH() {
|
||||||
return this.RDATA.length;
|
return this.RDATA.length;
|
||||||
}
|
}
|
||||||
|
serialize() {
|
||||||
|
// TODO: Implement compression
|
||||||
|
let name = serializeName(this.NAME);
|
||||||
|
let data = Buffer.alloc(name.length + 10 + this.RDLENGTH); // For TYPE, CLASS, TTL, RLENGTH
|
||||||
|
name.copy(data, 0, 0, name.length);
|
||||||
|
let offset = name.length;
|
||||||
|
data.writeUInt16BE(this.TYPE, offset);
|
||||||
|
offset += 2;
|
||||||
|
data.writeUInt16BE(this.CLASS, offset);
|
||||||
|
offset += 2;
|
||||||
|
data.writeUInt32BE(this._TTL, offset);
|
||||||
|
offset += 4;
|
||||||
|
data.writeUInt16BE(this.RDLENGTH, offset);
|
||||||
|
offset += 2;
|
||||||
|
this.RDATA.copy(data, offset, 0, this.RDLENGTH);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exports.RecourceRecord = RecourceRecord;
|
exports.RecourceRecord = RecourceRecord;
|
||||||
//# sourceMappingURL=request.js.map
|
//# sourceMappingURL=request.js.map
|
File diff suppressed because one or more lines are too long
102
lib/types.d.ts
vendored
102
lib/types.d.ts
vendored
@ -25,7 +25,97 @@ export declare enum ErrorCodes {
|
|||||||
*/
|
*/
|
||||||
Refused = 5,
|
Refused = 5,
|
||||||
}
|
}
|
||||||
export interface MessageHeader {
|
export declare enum QueryTypes {
|
||||||
|
/**
|
||||||
|
* IPv4 address
|
||||||
|
*/
|
||||||
|
A = 1,
|
||||||
|
/**
|
||||||
|
* Nameserver
|
||||||
|
*/
|
||||||
|
NS = 2,
|
||||||
|
/**
|
||||||
|
* Obsolete
|
||||||
|
*/
|
||||||
|
MD = 3,
|
||||||
|
/**
|
||||||
|
* Obsolete
|
||||||
|
*/
|
||||||
|
MF = 4,
|
||||||
|
/**
|
||||||
|
* Alias
|
||||||
|
*/
|
||||||
|
CNAME = 5,
|
||||||
|
/**
|
||||||
|
* Start of authority
|
||||||
|
*/
|
||||||
|
SOA = 6,
|
||||||
|
/**
|
||||||
|
* Experimental
|
||||||
|
*/
|
||||||
|
MB = 7,
|
||||||
|
/**
|
||||||
|
* Experimental
|
||||||
|
*/
|
||||||
|
MG = 8,
|
||||||
|
/**
|
||||||
|
* Experimental
|
||||||
|
*/
|
||||||
|
MR = 9,
|
||||||
|
/**
|
||||||
|
* Experimental
|
||||||
|
*/
|
||||||
|
NULL = 10,
|
||||||
|
/**
|
||||||
|
* Service description
|
||||||
|
*/
|
||||||
|
WKS = 11,
|
||||||
|
/**
|
||||||
|
* Reverse entry (inaddr.arpa)
|
||||||
|
*/
|
||||||
|
PTR = 12,
|
||||||
|
/**
|
||||||
|
* Host information
|
||||||
|
*/
|
||||||
|
HINFO = 13,
|
||||||
|
/**
|
||||||
|
* Mailbox / Mail-list information
|
||||||
|
*/
|
||||||
|
MINFO = 14,
|
||||||
|
/**
|
||||||
|
* Mail exchange
|
||||||
|
*/
|
||||||
|
MX = 15,
|
||||||
|
/**
|
||||||
|
* Text strings
|
||||||
|
*/
|
||||||
|
TXT = 16,
|
||||||
|
/**
|
||||||
|
* IPv6 address
|
||||||
|
*/
|
||||||
|
AAAA = 28,
|
||||||
|
/**
|
||||||
|
* SRV records
|
||||||
|
*/
|
||||||
|
SRV = 33,
|
||||||
|
/**
|
||||||
|
* Request to transfer entire zone
|
||||||
|
*/
|
||||||
|
AXFR = 252,
|
||||||
|
/**
|
||||||
|
* Request for mailbox related records
|
||||||
|
*/
|
||||||
|
MAILA = 254,
|
||||||
|
/**
|
||||||
|
* Request for mail agend RRs
|
||||||
|
*/
|
||||||
|
MAILB = 253,
|
||||||
|
/**
|
||||||
|
* Any class
|
||||||
|
*/
|
||||||
|
ANY = 255,
|
||||||
|
}
|
||||||
|
export interface IMessageHeader {
|
||||||
/**
|
/**
|
||||||
* A 16 bit identifier assigned by the program that
|
* A 16 bit identifier assigned by the program that
|
||||||
* generates any kind of query. This identifier is copied
|
* generates any kind of query. This identifier is copied
|
||||||
@ -73,7 +163,7 @@ export interface MessageHeader {
|
|||||||
/**
|
/**
|
||||||
* Reserved for future usage, must be 0 in all queries
|
* Reserved for future usage, must be 0 in all queries
|
||||||
*/
|
*/
|
||||||
Z: 0;
|
Z: 0 | 1;
|
||||||
AD: 0 | 1;
|
AD: 0 | 1;
|
||||||
CD: 0 | 1;
|
CD: 0 | 1;
|
||||||
/**
|
/**
|
||||||
@ -109,7 +199,7 @@ export interface MessageHeader {
|
|||||||
*/
|
*/
|
||||||
ARCOUNT: number;
|
ARCOUNT: number;
|
||||||
}
|
}
|
||||||
export interface MessageQuestion {
|
export interface IMessageQuestion {
|
||||||
/**
|
/**
|
||||||
* Domain name represented as sequence of labels
|
* Domain name represented as sequence of labels
|
||||||
* Each label consists of a length octed followed
|
* Each label consists of a length octed followed
|
||||||
@ -169,9 +259,9 @@ export interface MessageRecourceRecord {
|
|||||||
*/
|
*/
|
||||||
RDATA: Buffer;
|
RDATA: Buffer;
|
||||||
}
|
}
|
||||||
export interface Message {
|
export interface IMessage {
|
||||||
header: MessageHeader;
|
header: IMessageHeader;
|
||||||
questions: MessageQuestion[];
|
questions: IMessageQuestion[];
|
||||||
answers: MessageRecourceRecord[];
|
answers: MessageRecourceRecord[];
|
||||||
authorities: MessageRecourceRecord[];
|
authorities: MessageRecourceRecord[];
|
||||||
additionals: MessageRecourceRecord[];
|
additionals: MessageRecourceRecord[];
|
||||||
|
99
lib/types.js
99
lib/types.js
@ -1,13 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
// 0 No error condition
|
|
||||||
// * 1 Format error - unable to interpret query
|
|
||||||
// * 2 Server failure - internal problem
|
|
||||||
// * 3 Name error - Only for authorative name server, domain name of query does not exist
|
|
||||||
// * 4 Not implemented - Request not supported
|
|
||||||
// * 5 Refused - Nameserver refuses request
|
|
||||||
// * 6 - 15 Reserved for future usage
|
|
||||||
// *
|
|
||||||
var ErrorCodes;
|
var ErrorCodes;
|
||||||
(function (ErrorCodes) {
|
(function (ErrorCodes) {
|
||||||
/**
|
/**
|
||||||
@ -35,4 +27,95 @@ var ErrorCodes;
|
|||||||
*/
|
*/
|
||||||
ErrorCodes[ErrorCodes["Refused"] = 5] = "Refused";
|
ErrorCodes[ErrorCodes["Refused"] = 5] = "Refused";
|
||||||
})(ErrorCodes = exports.ErrorCodes || (exports.ErrorCodes = {}));
|
})(ErrorCodes = exports.ErrorCodes || (exports.ErrorCodes = {}));
|
||||||
|
var QueryTypes;
|
||||||
|
(function (QueryTypes) {
|
||||||
|
/**
|
||||||
|
* IPv4 address
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["A"] = 1] = "A";
|
||||||
|
/**
|
||||||
|
* Nameserver
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["NS"] = 2] = "NS";
|
||||||
|
/**
|
||||||
|
* Obsolete
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["MD"] = 3] = "MD";
|
||||||
|
/**
|
||||||
|
* Obsolete
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["MF"] = 4] = "MF";
|
||||||
|
/**
|
||||||
|
* Alias
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["CNAME"] = 5] = "CNAME";
|
||||||
|
/**
|
||||||
|
* Start of authority
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["SOA"] = 6] = "SOA";
|
||||||
|
/**
|
||||||
|
* Experimental
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["MB"] = 7] = "MB";
|
||||||
|
/**
|
||||||
|
* Experimental
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["MG"] = 8] = "MG";
|
||||||
|
/**
|
||||||
|
* Experimental
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["MR"] = 9] = "MR";
|
||||||
|
/**
|
||||||
|
* Experimental
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["NULL"] = 10] = "NULL";
|
||||||
|
/**
|
||||||
|
* Service description
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["WKS"] = 11] = "WKS";
|
||||||
|
/**
|
||||||
|
* Reverse entry (inaddr.arpa)
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["PTR"] = 12] = "PTR";
|
||||||
|
/**
|
||||||
|
* Host information
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["HINFO"] = 13] = "HINFO";
|
||||||
|
/**
|
||||||
|
* Mailbox / Mail-list information
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["MINFO"] = 14] = "MINFO";
|
||||||
|
/**
|
||||||
|
* Mail exchange
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["MX"] = 15] = "MX";
|
||||||
|
/**
|
||||||
|
* Text strings
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["TXT"] = 16] = "TXT";
|
||||||
|
/**
|
||||||
|
* IPv6 address
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["AAAA"] = 28] = "AAAA";
|
||||||
|
/**
|
||||||
|
* SRV records
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["SRV"] = 33] = "SRV";
|
||||||
|
/**
|
||||||
|
* Request to transfer entire zone
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["AXFR"] = 252] = "AXFR";
|
||||||
|
/**
|
||||||
|
* Request for mailbox related records
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["MAILA"] = 254] = "MAILA";
|
||||||
|
/**
|
||||||
|
* Request for mail agend RRs
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["MAILB"] = 253] = "MAILB";
|
||||||
|
/**
|
||||||
|
* Any class
|
||||||
|
*/
|
||||||
|
QueryTypes[QueryTypes["ANY"] = 255] = "ANY";
|
||||||
|
})(QueryTypes = exports.QueryTypes || (exports.QueryTypes = {}));
|
||||||
//# sourceMappingURL=types.js.map
|
//# sourceMappingURL=types.js.map
|
@ -1 +1 @@
|
|||||||
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;AACA,uBAAuB;AACvB,kDAAkD;AAClD,8CAA8C;AAC9C,kGAAkG;AAClG,0DAA0D;AAC1D,0DAA0D;AAC1D,wCAAwC;AACxC,WAAW;AACX,IAAY,UA8BX;AA9BD,WAAY,UAAU;IACnB;;OAEG;IACH,iDAAW,CAAA;IAEX;;OAEG;IACH,yDAAW,CAAA;IAEX;;OAEG;IACH,6DAAa,CAAA;IAEb;;OAEG;IACH,qDAAS,CAAA;IAET;;OAEG;IACH,+DAAc,CAAA;IAEd;;OAEG;IACH,iDAAO,CAAA;AACV,CAAC,EA9BW,UAAU,GAAV,kBAAU,KAAV,kBAAU,QA8BrB"}
|
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;AAAA,IAAY,UA8BX;AA9BD,WAAY,UAAU;IACnB;;OAEG;IACH,iDAAW,CAAA;IAEX;;OAEG;IACH,yDAAW,CAAA;IAEX;;OAEG;IACH,6DAAa,CAAA;IAEb;;OAEG;IACH,qDAAS,CAAA;IAET;;OAEG;IACH,+DAAc,CAAA;IAEd;;OAEG;IACH,iDAAO,CAAA;AACV,CAAC,EA9BW,UAAU,GAAV,kBAAU,KAAV,kBAAU,QA8BrB;AAED,IAAY,UA8GX;AA9GD,WAAY,UAAU;IACnB;;OAEG;IACH,qCAAQ,CAAA;IAER;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,6CAAY,CAAA;IAEZ;;OAEG;IACH,yCAAU,CAAA;IAEV;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,4CAAW,CAAA;IAEX;;OAEG;IACH,0CAAU,CAAA;IAEV;;OAEG;IACH,0CAAU,CAAA;IAEV;;OAEG;IACH,8CAAY,CAAA;IAEZ;;OAEG;IACH,8CAAY,CAAA;IAEZ;;OAEG;IACH,wCAAS,CAAA;IAET;;OAEG;IACH,0CAAU,CAAA;IAEV;;OAEG;IACH,4CAAW,CAAA;IAEX;;OAEG;IACH,0CAAU,CAAA;IAEV;;OAEG;IACH,6CAAW,CAAA;IAEX;;OAEG;IACH,+CAAY,CAAA;IAEZ;;OAEG;IACH,+CAAY,CAAA;IAEZ;;OAEG;IACH,2CAAU,CAAA;AACb,CAAC,EA9GW,UAAU,GAAV,kBAAU,KAAV,kBAAU,QA8GrB"}
|
319
src/request.ts
319
src/request.ts
@ -1,121 +1,7 @@
|
|||||||
import { Parser } from "binary-parser"
|
import { Parser } from "binary-parser"
|
||||||
import { Message, MessageHeader, MessageQuestion, MessageRecourceRecord, ErrorCodes } from "./types"
|
import { IMessage, IMessageHeader, IMessageQuestion, MessageRecourceRecord, ErrorCodes } from "./types"
|
||||||
|
|
||||||
const MAX_LABEL_SIZE = 63;
|
const headerParser: Parser = new Parser()
|
||||||
|
|
||||||
export enum QueryTypes {
|
|
||||||
/**
|
|
||||||
* IPv4 address
|
|
||||||
*/
|
|
||||||
A = 0x01,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Nameserver
|
|
||||||
*/
|
|
||||||
NS = 0x02, // nameserver
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obsolete
|
|
||||||
*/
|
|
||||||
MD = 0x03, // obsolete
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obsolete
|
|
||||||
*/
|
|
||||||
MF = 0x04, // obsolete
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alias
|
|
||||||
*/
|
|
||||||
CNAME = 0x05, // alias
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start of authority
|
|
||||||
*/
|
|
||||||
SOA = 0x06, // start of authority
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Experimental
|
|
||||||
*/
|
|
||||||
MB = 0x07, // experimental
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Experimental
|
|
||||||
*/
|
|
||||||
MG = 0x08, // experimental
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Experimental
|
|
||||||
*/
|
|
||||||
MR = 0x09, // experimental
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Experimental
|
|
||||||
*/
|
|
||||||
NULL = 0x0A, // experimental null RR
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service description
|
|
||||||
*/
|
|
||||||
WKS = 0x0B, // service description
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse entry (inaddr.arpa)
|
|
||||||
*/
|
|
||||||
PTR = 0x0C, // reverse entry (inaddr.arpa)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Host information
|
|
||||||
*/
|
|
||||||
HINFO = 0x0D, // host information
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mailbox / Mail-list information
|
|
||||||
*/
|
|
||||||
MINFO = 0x0E, // mailbox or mail list information
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mail exchange
|
|
||||||
*/
|
|
||||||
MX = 0x0F, // mail exchange
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text strings
|
|
||||||
*/
|
|
||||||
TXT = 0x10, // text strings
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IPv6 address
|
|
||||||
*/
|
|
||||||
AAAA = 0x1C, // ipv6 address
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SRV records
|
|
||||||
*/
|
|
||||||
SRV = 0x21, // srv records
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request to transfer entire zone
|
|
||||||
*/
|
|
||||||
AXFR = 0xFC, // request to transfer entire zone
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request for mailbox related records
|
|
||||||
*/
|
|
||||||
MAILA = 0xFE, // request for mailbox related records
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request for mail agend RRs
|
|
||||||
*/
|
|
||||||
MAILB = 0xFD, // request for mail agent RRs
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Any class
|
|
||||||
*/
|
|
||||||
ANY = 0xFF, // any class
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerParser = new Parser()
|
|
||||||
.endianess("big")
|
.endianess("big")
|
||||||
.uint16("ID")
|
.uint16("ID")
|
||||||
.bit1("QR")
|
.bit1("QR")
|
||||||
@ -133,6 +19,14 @@ const headerParser = new Parser()
|
|||||||
.uint16("NSCOUNT")
|
.uint16("NSCOUNT")
|
||||||
.uint16("ARCOUNT")
|
.uint16("ARCOUNT")
|
||||||
|
|
||||||
|
export function parseHeader(data: Buffer): IMessageHeader {
|
||||||
|
try {
|
||||||
|
return <any>headerParser.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("Header parsing failed" + e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const labelParser = new Parser()
|
const labelParser = new Parser()
|
||||||
.endianess("big")
|
.endianess("big")
|
||||||
.uint8("dataLength")
|
.uint8("dataLength")
|
||||||
@ -156,16 +50,22 @@ const questionParser = new Parser()
|
|||||||
.uint16("QTYPE")
|
.uint16("QTYPE")
|
||||||
.uint16("QCLASS")
|
.uint16("QCLASS")
|
||||||
|
|
||||||
function parseQuestions(count: number, packet: Buffer): MessageQuestion[] {
|
export function parseQuestions(count: number, packet: Buffer): IMessageQuestion[] {
|
||||||
|
try {
|
||||||
return <any>new Parser()
|
return <any>new Parser()
|
||||||
.endianess("big")
|
.endianess("big")
|
||||||
.array("questions", {
|
.array("questions", {
|
||||||
type: questionParser,
|
type: questionParser,
|
||||||
length: count
|
length: count
|
||||||
}).parse(packet).questions;
|
}).parse(packet).questions;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("Question parsing failed" + e.message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SerializeName(name: string) {
|
const MAX_LABEL_SIZE = 63;
|
||||||
|
|
||||||
|
export function serializeName(name: string) {
|
||||||
let length = 0;
|
let length = 0;
|
||||||
let parts = name.split(".");
|
let parts = name.split(".");
|
||||||
parts.forEach(e => {
|
parts.forEach(e => {
|
||||||
@ -187,33 +87,32 @@ export function SerializeName(name: string) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Request implements Message {
|
export class Request implements IMessage {
|
||||||
private _header: MessageHeader;
|
private _header: Header;
|
||||||
get header() {
|
get header() {
|
||||||
return Object.assign({}, this._header);
|
return Object.assign({}, this._header);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _questions: MessageQuestion[];
|
private _questions: Question[];
|
||||||
get questions() {
|
get questions() {
|
||||||
return this._questions.map(e => e);
|
return this._questions.map(e => Object.assign({}, e));
|
||||||
}
|
}
|
||||||
|
|
||||||
answers: RecourceRecord[] = [];
|
answers: RecourceRecord[] = [];
|
||||||
authorities: RecourceRecord[] = [];
|
authorities: RecourceRecord[] = [];
|
||||||
additionals: RecourceRecord[] = [];
|
additionals: RecourceRecord[] = [];
|
||||||
|
|
||||||
constructor(packet: Buffer, private sendCallback: (packet: Buffer) => any) {
|
constructor(packet: Buffer, private sendCallback: (packet: Buffer) => any, private max_size = 512) {
|
||||||
let headerData = Buffer.alloc(12);
|
let headerData = Buffer.alloc(12);
|
||||||
packet.copy(headerData, 0, 0, 12);
|
packet.copy(headerData, 0, 0, 12);
|
||||||
let bodyData = Buffer.alloc(packet.length - 12);
|
let bodyData = Buffer.alloc(packet.length - 12);
|
||||||
packet.copy(bodyData, 0, 12, packet.length);
|
packet.copy(bodyData, 0, 12, packet.length);
|
||||||
|
|
||||||
this._header = <any>headerParser.parse(headerData);
|
this._header = new Header(parseHeader(headerData));
|
||||||
this._header.AD = 0;
|
this._header.AD = 0;
|
||||||
this._header.RCODE = ErrorCodes.NoError;
|
this._header.RCODE = ErrorCodes.NoError;
|
||||||
this._header.RA = this._header.RD;
|
this._header.RA = this._header.RD;
|
||||||
|
this._questions = parseQuestions(this._header.QDCOUNT, bodyData).map(e => new Question(e));
|
||||||
this._questions = parseQuestions(this._header.QDCOUNT, bodyData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
error(error: ErrorCodes) {
|
error(error: ErrorCodes) {
|
||||||
@ -229,50 +128,53 @@ export class Request implements Message {
|
|||||||
this.sendCallback(this.serialize());
|
this.sendCallback(this.serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(truncate: boolean = false, rcode: 0 | 1 | 2 | 3 | 4 | 5 = 0) {
|
serialize() {
|
||||||
this._header.AA = 1;
|
this._header.AA = 1;
|
||||||
this._header.ANCOUNT = this.answers.length;
|
this._header.ANCOUNT = this.answers.length;
|
||||||
this._header.ARCOUNT = this.additionals.length;
|
this._header.ARCOUNT = this.additionals.length;
|
||||||
this._header.NSCOUNT = this.authorities.length;
|
this._header.NSCOUNT = this.authorities.length;
|
||||||
this._header.QR = 1;
|
this._header.QR = 1;
|
||||||
this._header.RCODE = rcode;
|
let questions = this._questions.map(e => e.serialize())
|
||||||
|
let answers = this.answers.map(e => e.serialize())
|
||||||
|
let authority = this.authorities.map(e => e.serialize())
|
||||||
|
let additional = this.additionals.map(e => e.serialize())
|
||||||
|
|
||||||
let questions = this.questions.map(this.serializeQuestion, this)
|
let length = 12;
|
||||||
let answers = this.answers.map(this.serializeResourceRecord, this)
|
questions.forEach(e => length += e.length);
|
||||||
let authority = this.authorities.map(this.serializeResourceRecord, this)
|
answers.forEach(e => length += e.length)
|
||||||
let additional = this.additionals.map(this.serializeResourceRecord, this)
|
authority.forEach(e => length += e.length)
|
||||||
|
additional.forEach(e => length += e.length)
|
||||||
|
// let questionsByteLength = 0;
|
||||||
|
// questions.forEach(e => questionsByteLength += e.length);
|
||||||
|
|
||||||
let questionsByteLength = 0;
|
// let answersByteLength = 0;
|
||||||
questions.forEach(e => questionsByteLength += e.length);
|
// answers.forEach(e => answersByteLength += e.length)
|
||||||
|
|
||||||
let answersByteLength = 0;
|
// let authorityByteLength = 0;
|
||||||
answers.forEach(e => answersByteLength += e.length)
|
// authority.forEach(e => authorityByteLength += e.length)
|
||||||
|
|
||||||
let authorityByteLength = 0;
|
// let additionalByteLength = 0;
|
||||||
authority.forEach(e => authorityByteLength += e.length)
|
// additional.forEach(e => additionalByteLength += e.length)
|
||||||
|
|
||||||
let additionalByteLength = 0;
|
// let length = 12 + questionsByteLength + answersByteLength + authorityByteLength + additionalByteLength; //Header is always 12 byte large
|
||||||
additional.forEach(e => additionalByteLength += e.length)
|
|
||||||
|
|
||||||
let length = 12 + questionsByteLength + answersByteLength + authorityByteLength + additionalByteLength; //Header is always 12 byte large
|
if (length > this.max_size) {
|
||||||
|
|
||||||
if (truncate && length > 512) {
|
|
||||||
this._header.TC = 1;
|
this._header.TC = 1;
|
||||||
|
|
||||||
//Buffer will ignore data that exeeds the max buffer length
|
//Will ignore data, that exceeds length
|
||||||
length = 512;
|
length = this.max_size;
|
||||||
}
|
}
|
||||||
|
let header = this._header.serialize();
|
||||||
let header = this.serializeHeader()
|
|
||||||
|
|
||||||
let data = Buffer.alloc(length)
|
let data = Buffer.alloc(length)
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
|
||||||
let append = (buffer: Buffer) => {
|
let append = (buffer: Buffer) => {
|
||||||
|
if (offset <= length) {
|
||||||
buffer.copy(data, offset, 0, buffer.length)
|
buffer.copy(data, offset, 0, buffer.length)
|
||||||
offset += buffer.length;
|
offset += buffer.length;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
append(header)
|
append(header)
|
||||||
questions.forEach(append)
|
questions.forEach(append)
|
||||||
answers.forEach(append)
|
answers.forEach(append)
|
||||||
@ -280,57 +182,73 @@ export class Request implements Message {
|
|||||||
additional.forEach(append)
|
additional.forEach(append)
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private serializeHeader() {
|
export class Header implements IMessageHeader {
|
||||||
let header = this.header;
|
ID: number;
|
||||||
let data = Buffer.alloc(12);
|
QR: 0 | 1;
|
||||||
data.writeUInt16BE(header.ID, 0);
|
OPCODE: number
|
||||||
var f = 0x0000;
|
AA: 0 | 1;
|
||||||
f = f | (header.QR << 15);
|
TC: 0 | 1;
|
||||||
f = f | (header.OPCODE << 11);
|
RD: 0 | 1;
|
||||||
f = f | (header.AA << 10);
|
RA: 0 | 1;
|
||||||
f = f | (header.TC << 9);
|
Z: 0 | 1;
|
||||||
f = f | (header.RD << 8);
|
AD: 0 | 1;
|
||||||
f = f | (header.RA << 7);
|
CD: 0 | 1;
|
||||||
f = f | (header.Z << 6);
|
RCODE: ErrorCodes;
|
||||||
f = f | (header.AD << 5);
|
QDCOUNT: number;
|
||||||
f = f | (header.CD << 4);
|
ANCOUNT: number;
|
||||||
f = f | header.RCODE;
|
NSCOUNT: number;
|
||||||
data.writeUInt16BE(f, 2);
|
ARCOUNT: number;
|
||||||
|
|
||||||
data.writeUInt16BE(header.QDCOUNT, 4)
|
constructor(header: IMessageHeader) {
|
||||||
data.writeUInt16BE(header.ANCOUNT, 6)
|
for (let k in header) {
|
||||||
data.writeUInt16BE(header.NSCOUNT, 8)
|
this[k] = header[k];
|
||||||
data.writeUInt16BE(header.ARCOUNT, 10)
|
}
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private serializeQuestion(question: MessageQuestion) {
|
serialize() {
|
||||||
let qname = SerializeName(question.QNAME);
|
let data = Buffer.alloc(12);
|
||||||
|
data.writeUInt16BE(this.ID, 0);
|
||||||
|
var f = 0x0000;
|
||||||
|
f = f | (this.QR << 15);
|
||||||
|
f = f | (this.OPCODE << 11);
|
||||||
|
f = f | (this.AA << 10);
|
||||||
|
f = f | (this.TC << 9);
|
||||||
|
f = f | (this.RD << 8);
|
||||||
|
f = f | (this.RA << 7);
|
||||||
|
f = f | (this.Z << 6);
|
||||||
|
f = f | (this.AD << 5);
|
||||||
|
f = f | (this.CD << 4);
|
||||||
|
f = f | this.RCODE;
|
||||||
|
data.writeUInt16BE(f, 2);
|
||||||
|
data.writeUInt16BE(this.QDCOUNT, 4)
|
||||||
|
data.writeUInt16BE(this.ANCOUNT, 6)
|
||||||
|
data.writeUInt16BE(this.NSCOUNT, 8)
|
||||||
|
data.writeUInt16BE(this.ARCOUNT, 10)
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Question implements IMessageQuestion {
|
||||||
|
QNAME: string;
|
||||||
|
QTYPE: number;
|
||||||
|
QCLASS: number;
|
||||||
|
|
||||||
|
constructor(question: IMessageQuestion) {
|
||||||
|
for (let k in question) {
|
||||||
|
this[k] = question[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
let qname = serializeName(this.QNAME);
|
||||||
let data = Buffer.alloc(qname.length + 4);
|
let data = Buffer.alloc(qname.length + 4);
|
||||||
qname.copy(data, 0, 0, qname.length);
|
qname.copy(data, 0, 0, qname.length);
|
||||||
let offset = qname.length;
|
let offset = qname.length;
|
||||||
data.writeUInt16BE(question.QTYPE, offset);
|
data.writeUInt16BE(this.QTYPE, offset);
|
||||||
offset += 2;
|
offset += 2;
|
||||||
data.writeUInt16BE(question.QCLASS, offset);
|
data.writeUInt16BE(this.QCLASS, offset);
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private serializeResourceRecord(record: MessageRecourceRecord) {
|
|
||||||
// TODO: Implement compression
|
|
||||||
let name = SerializeName(record.NAME);
|
|
||||||
let data = Buffer.alloc(name.length + 10 + record.RDLENGTH) // For TYPE, CLASS, TTL, RLENGTH
|
|
||||||
name.copy(data, 0, 0, name.length);
|
|
||||||
let offset = name.length;
|
|
||||||
data.writeUInt16BE(record.TYPE, offset)
|
|
||||||
offset += 2
|
|
||||||
data.writeUInt16BE(record.CLASS, offset)
|
|
||||||
offset += 2
|
|
||||||
data.writeUInt32BE(record.TTL, offset)
|
|
||||||
offset += 4
|
|
||||||
data.writeUInt16BE(record.RDLENGTH, offset)
|
|
||||||
offset += 2
|
|
||||||
record.RDATA.copy(data, offset, 0, record.RDLENGTH)
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,6 +283,7 @@ export class RecourceRecord implements MessageRecourceRecord {
|
|||||||
private _TTL: number;
|
private _TTL: number;
|
||||||
set TTL(value) {
|
set TTL(value) {
|
||||||
if (value < 0 || value > 4294967295) throw new TypeError("TTL Range: 0 - 4.294.967.295")
|
if (value < 0 || value > 4294967295) throw new TypeError("TTL Range: 0 - 4.294.967.295")
|
||||||
|
this._TTL = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
get TTL() {
|
get TTL() {
|
||||||
@ -376,4 +295,22 @@ export class RecourceRecord implements MessageRecourceRecord {
|
|||||||
get RDLENGTH() {
|
get RDLENGTH() {
|
||||||
return this.RDATA.length;
|
return this.RDATA.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public serialize() {
|
||||||
|
// TODO: Implement compression
|
||||||
|
let name = serializeName(this.NAME);
|
||||||
|
let data = Buffer.alloc(name.length + 10 + this.RDLENGTH) // For TYPE, CLASS, TTL, RLENGTH
|
||||||
|
name.copy(data, 0, 0, name.length);
|
||||||
|
let offset = name.length;
|
||||||
|
data.writeUInt16BE(this.TYPE, offset)
|
||||||
|
offset += 2
|
||||||
|
data.writeUInt16BE(this.CLASS, offset)
|
||||||
|
offset += 2
|
||||||
|
data.writeUInt32BE(this._TTL, offset)
|
||||||
|
offset += 4
|
||||||
|
data.writeUInt16BE(this.RDLENGTH, offset)
|
||||||
|
offset += 2
|
||||||
|
this.RDATA.copy(data, offset, 0, this.RDLENGTH)
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
133
src/types.ts
133
src/types.ts
@ -1,12 +1,3 @@
|
|||||||
|
|
||||||
// 0 No error condition
|
|
||||||
// * 1 Format error - unable to interpret query
|
|
||||||
// * 2 Server failure - internal problem
|
|
||||||
// * 3 Name error - Only for authorative name server, domain name of query does not exist
|
|
||||||
// * 4 Not implemented - Request not supported
|
|
||||||
// * 5 Refused - Nameserver refuses request
|
|
||||||
// * 6 - 15 Reserved for future usage
|
|
||||||
// *
|
|
||||||
export enum ErrorCodes {
|
export enum ErrorCodes {
|
||||||
/**
|
/**
|
||||||
* No error
|
* No error
|
||||||
@ -39,7 +30,119 @@ export enum ErrorCodes {
|
|||||||
Refused
|
Refused
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageHeader {
|
export enum QueryTypes {
|
||||||
|
/**
|
||||||
|
* IPv4 address
|
||||||
|
*/
|
||||||
|
A = 0x01,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nameserver
|
||||||
|
*/
|
||||||
|
NS = 0x02, // nameserver
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obsolete
|
||||||
|
*/
|
||||||
|
MD = 0x03, // obsolete
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obsolete
|
||||||
|
*/
|
||||||
|
MF = 0x04, // obsolete
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias
|
||||||
|
*/
|
||||||
|
CNAME = 0x05, // alias
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start of authority
|
||||||
|
*/
|
||||||
|
SOA = 0x06, // start of authority
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Experimental
|
||||||
|
*/
|
||||||
|
MB = 0x07, // experimental
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Experimental
|
||||||
|
*/
|
||||||
|
MG = 0x08, // experimental
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Experimental
|
||||||
|
*/
|
||||||
|
MR = 0x09, // experimental
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Experimental
|
||||||
|
*/
|
||||||
|
NULL = 0x0A, // experimental null RR
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service description
|
||||||
|
*/
|
||||||
|
WKS = 0x0B, // service description
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse entry (inaddr.arpa)
|
||||||
|
*/
|
||||||
|
PTR = 0x0C, // reverse entry (inaddr.arpa)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Host information
|
||||||
|
*/
|
||||||
|
HINFO = 0x0D, // host information
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mailbox / Mail-list information
|
||||||
|
*/
|
||||||
|
MINFO = 0x0E, // mailbox or mail list information
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mail exchange
|
||||||
|
*/
|
||||||
|
MX = 0x0F, // mail exchange
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text strings
|
||||||
|
*/
|
||||||
|
TXT = 0x10, // text strings
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPv6 address
|
||||||
|
*/
|
||||||
|
AAAA = 0x1C, // ipv6 address
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SRV records
|
||||||
|
*/
|
||||||
|
SRV = 0x21, // srv records
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request to transfer entire zone
|
||||||
|
*/
|
||||||
|
AXFR = 0xFC, // request to transfer entire zone
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request for mailbox related records
|
||||||
|
*/
|
||||||
|
MAILA = 0xFE, // request for mailbox related records
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request for mail agend RRs
|
||||||
|
*/
|
||||||
|
MAILB = 0xFD, // request for mail agent RRs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any class
|
||||||
|
*/
|
||||||
|
ANY = 0xFF, // any class
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMessageHeader {
|
||||||
/**
|
/**
|
||||||
* A 16 bit identifier assigned by the program that
|
* A 16 bit identifier assigned by the program that
|
||||||
* generates any kind of query. This identifier is copied
|
* generates any kind of query. This identifier is copied
|
||||||
@ -93,7 +196,7 @@ export interface MessageHeader {
|
|||||||
/**
|
/**
|
||||||
* Reserved for future usage, must be 0 in all queries
|
* Reserved for future usage, must be 0 in all queries
|
||||||
*/
|
*/
|
||||||
Z: 0;
|
Z: 0 | 1;
|
||||||
|
|
||||||
AD: 0 | 1;
|
AD: 0 | 1;
|
||||||
|
|
||||||
@ -136,7 +239,7 @@ export interface MessageHeader {
|
|||||||
*/
|
*/
|
||||||
ARCOUNT: number;
|
ARCOUNT: number;
|
||||||
}
|
}
|
||||||
export interface MessageQuestion {
|
export interface IMessageQuestion {
|
||||||
/**
|
/**
|
||||||
* Domain name represented as sequence of labels
|
* Domain name represented as sequence of labels
|
||||||
* Each label consists of a length octed followed
|
* Each label consists of a length octed followed
|
||||||
@ -203,9 +306,9 @@ export interface MessageRecourceRecord {
|
|||||||
*/
|
*/
|
||||||
RDATA: Buffer;
|
RDATA: Buffer;
|
||||||
}
|
}
|
||||||
export interface Message {
|
export interface IMessage {
|
||||||
header: MessageHeader;
|
header: IMessageHeader;
|
||||||
questions: MessageQuestion[];
|
questions: IMessageQuestion[];
|
||||||
answers: MessageRecourceRecord[];
|
answers: MessageRecourceRecord[];
|
||||||
authorities: MessageRecourceRecord[];
|
authorities: MessageRecourceRecord[];
|
||||||
additionals: MessageRecourceRecord[];
|
additionals: MessageRecourceRecord[];
|
||||||
|
Loading…
Reference in New Issue
Block a user