Refactoring and adding Question class
This commit is contained in:
335
src/request.ts
335
src/request.ts
@ -1,121 +1,7 @@
|
||||
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;
|
||||
|
||||
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()
|
||||
const headerParser: Parser = new Parser()
|
||||
.endianess("big")
|
||||
.uint16("ID")
|
||||
.bit1("QR")
|
||||
@ -133,6 +19,14 @@ const headerParser = new Parser()
|
||||
.uint16("NSCOUNT")
|
||||
.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()
|
||||
.endianess("big")
|
||||
.uint8("dataLength")
|
||||
@ -156,16 +50,22 @@ const questionParser = new Parser()
|
||||
.uint16("QTYPE")
|
||||
.uint16("QCLASS")
|
||||
|
||||
function parseQuestions(count: number, packet: Buffer): MessageQuestion[] {
|
||||
return <any>new Parser()
|
||||
.endianess("big")
|
||||
.array("questions", {
|
||||
type: questionParser,
|
||||
length: count
|
||||
}).parse(packet).questions;
|
||||
export function parseQuestions(count: number, packet: Buffer): IMessageQuestion[] {
|
||||
try {
|
||||
return <any>new Parser()
|
||||
.endianess("big")
|
||||
.array("questions", {
|
||||
type: questionParser,
|
||||
length: count
|
||||
}).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 parts = name.split(".");
|
||||
parts.forEach(e => {
|
||||
@ -187,33 +87,32 @@ export function SerializeName(name: string) {
|
||||
return data;
|
||||
}
|
||||
|
||||
export class Request implements Message {
|
||||
private _header: MessageHeader;
|
||||
export class Request implements IMessage {
|
||||
private _header: Header;
|
||||
get header() {
|
||||
return Object.assign({}, this._header);
|
||||
}
|
||||
|
||||
private _questions: MessageQuestion[];
|
||||
private _questions: Question[];
|
||||
get questions() {
|
||||
return this._questions.map(e => e);
|
||||
return this._questions.map(e => Object.assign({}, e));
|
||||
}
|
||||
|
||||
answers: RecourceRecord[] = [];
|
||||
authorities: 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);
|
||||
packet.copy(headerData, 0, 0, 12);
|
||||
let bodyData = Buffer.alloc(packet.length - 12);
|
||||
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.RCODE = ErrorCodes.NoError;
|
||||
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));
|
||||
}
|
||||
|
||||
error(error: ErrorCodes) {
|
||||
@ -229,50 +128,53 @@ export class Request implements Message {
|
||||
this.sendCallback(this.serialize());
|
||||
}
|
||||
|
||||
serialize(truncate: boolean = false, rcode: 0 | 1 | 2 | 3 | 4 | 5 = 0) {
|
||||
serialize() {
|
||||
this._header.AA = 1;
|
||||
this._header.ANCOUNT = this.answers.length;
|
||||
this._header.ARCOUNT = this.additionals.length;
|
||||
this._header.NSCOUNT = this.authorities.length;
|
||||
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 answers = this.answers.map(this.serializeResourceRecord, this)
|
||||
let authority = this.authorities.map(this.serializeResourceRecord, this)
|
||||
let additional = this.additionals.map(this.serializeResourceRecord, this)
|
||||
let length = 12;
|
||||
questions.forEach(e => length += e.length);
|
||||
answers.forEach(e => length += e.length)
|
||||
authority.forEach(e => length += e.length)
|
||||
additional.forEach(e => length += e.length)
|
||||
// let questionsByteLength = 0;
|
||||
// questions.forEach(e => questionsByteLength += e.length);
|
||||
|
||||
let questionsByteLength = 0;
|
||||
questions.forEach(e => questionsByteLength += e.length);
|
||||
// let answersByteLength = 0;
|
||||
// answers.forEach(e => answersByteLength += e.length)
|
||||
|
||||
let answersByteLength = 0;
|
||||
answers.forEach(e => answersByteLength += e.length)
|
||||
// let authorityByteLength = 0;
|
||||
// authority.forEach(e => authorityByteLength += e.length)
|
||||
|
||||
let authorityByteLength = 0;
|
||||
authority.forEach(e => authorityByteLength += e.length)
|
||||
// let additionalByteLength = 0;
|
||||
// additional.forEach(e => additionalByteLength += e.length)
|
||||
|
||||
let additionalByteLength = 0;
|
||||
additional.forEach(e => additionalByteLength += e.length)
|
||||
// let length = 12 + questionsByteLength + answersByteLength + authorityByteLength + additionalByteLength; //Header is always 12 byte large
|
||||
|
||||
let length = 12 + questionsByteLength + answersByteLength + authorityByteLength + additionalByteLength; //Header is always 12 byte large
|
||||
|
||||
if (truncate && length > 512) {
|
||||
if (length > this.max_size) {
|
||||
this._header.TC = 1;
|
||||
|
||||
//Buffer will ignore data that exeeds the max buffer length
|
||||
length = 512;
|
||||
//Will ignore data, that exceeds length
|
||||
length = this.max_size;
|
||||
}
|
||||
|
||||
let header = this.serializeHeader()
|
||||
let header = this._header.serialize();
|
||||
|
||||
let data = Buffer.alloc(length)
|
||||
let offset = 0;
|
||||
|
||||
let append = (buffer: Buffer) => {
|
||||
buffer.copy(data, offset, 0, buffer.length)
|
||||
offset += buffer.length;
|
||||
if (offset <= length) {
|
||||
buffer.copy(data, offset, 0, buffer.length)
|
||||
offset += buffer.length;
|
||||
}
|
||||
}
|
||||
|
||||
append(header)
|
||||
questions.forEach(append)
|
||||
answers.forEach(append)
|
||||
@ -280,57 +182,73 @@ export class Request implements Message {
|
||||
additional.forEach(append)
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
private serializeHeader() {
|
||||
let header = this.header;
|
||||
let data = Buffer.alloc(12);
|
||||
data.writeUInt16BE(header.ID, 0);
|
||||
var f = 0x0000;
|
||||
f = f | (header.QR << 15);
|
||||
f = f | (header.OPCODE << 11);
|
||||
f = f | (header.AA << 10);
|
||||
f = f | (header.TC << 9);
|
||||
f = f | (header.RD << 8);
|
||||
f = f | (header.RA << 7);
|
||||
f = f | (header.Z << 6);
|
||||
f = f | (header.AD << 5);
|
||||
f = f | (header.CD << 4);
|
||||
f = f | header.RCODE;
|
||||
data.writeUInt16BE(f, 2);
|
||||
export class Header implements IMessageHeader {
|
||||
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;
|
||||
|
||||
data.writeUInt16BE(header.QDCOUNT, 4)
|
||||
data.writeUInt16BE(header.ANCOUNT, 6)
|
||||
data.writeUInt16BE(header.NSCOUNT, 8)
|
||||
data.writeUInt16BE(header.ARCOUNT, 10)
|
||||
return data;
|
||||
constructor(header: IMessageHeader) {
|
||||
for (let k in header) {
|
||||
this[k] = header[k];
|
||||
}
|
||||
}
|
||||
|
||||
private serializeQuestion(question: MessageQuestion) {
|
||||
let qname = SerializeName(question.QNAME);
|
||||
serialize() {
|
||||
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);
|
||||
qname.copy(data, 0, 0, qname.length);
|
||||
let offset = qname.length;
|
||||
data.writeUInt16BE(question.QTYPE, offset);
|
||||
data.writeUInt16BE(this.QTYPE, offset);
|
||||
offset += 2;
|
||||
data.writeUInt16BE(question.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)
|
||||
data.writeUInt16BE(this.QCLASS, offset);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@ -365,6 +283,7 @@ export class RecourceRecord implements MessageRecourceRecord {
|
||||
private _TTL: number;
|
||||
set TTL(value) {
|
||||
if (value < 0 || value > 4294967295) throw new TypeError("TTL Range: 0 - 4.294.967.295")
|
||||
this._TTL = value;
|
||||
}
|
||||
|
||||
get TTL() {
|
||||
@ -376,4 +295,22 @@ export class RecourceRecord implements MessageRecourceRecord {
|
||||
get RDLENGTH() {
|
||||
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 {
|
||||
/**
|
||||
* No error
|
||||
@ -39,7 +30,119 @@ export enum ErrorCodes {
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
Z: 0;
|
||||
Z: 0 | 1;
|
||||
|
||||
AD: 0 | 1;
|
||||
|
||||
@ -136,7 +239,7 @@ export interface MessageHeader {
|
||||
*/
|
||||
ARCOUNT: number;
|
||||
}
|
||||
export interface MessageQuestion {
|
||||
export interface IMessageQuestion {
|
||||
/**
|
||||
* Domain name represented as sequence of labels
|
||||
* Each label consists of a length octed followed
|
||||
@ -203,9 +306,9 @@ export interface MessageRecourceRecord {
|
||||
*/
|
||||
RDATA: Buffer;
|
||||
}
|
||||
export interface Message {
|
||||
header: MessageHeader;
|
||||
questions: MessageQuestion[];
|
||||
export interface IMessage {
|
||||
header: IMessageHeader;
|
||||
questions: IMessageQuestion[];
|
||||
answers: MessageRecourceRecord[];
|
||||
authorities: MessageRecourceRecord[];
|
||||
additionals: MessageRecourceRecord[];
|
||||
|
Reference in New Issue
Block a user