commit 524f24639818f00e03c12c6dca1da95a4e75b874 Author: Fabian Stamm Date: Sun May 13 12:07:43 2018 +0200 First basic implementation of protocol. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c244159 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +yarn.lock +*.pcapng +*.bin \ No newline at end of file diff --git a/lib/listener.d.ts b/lib/listener.d.ts new file mode 100644 index 0000000..640194e --- /dev/null +++ b/lib/listener.d.ts @@ -0,0 +1,7 @@ +import { Request } from "./request"; +export default class Listener { + private udp; + private tcp; + constructor(type: "udp" | "tcp", onRequest: (request: Request) => any, port: number, host?: string); + close(): void; +} diff --git a/lib/listener.js b/lib/listener.js new file mode 100644 index 0000000..c500e85 --- /dev/null +++ b/lib/listener.js @@ -0,0 +1,69 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const net = require("net"); +const dgram = require("dgram"); +const request_1 = require("./request"); +class Listener { + constructor(type, onRequest, port, host = "0.0.0.0") { + switch (type) { + case "udp": + this.udp = dgram.createSocket("udp4"); + this.udp.on("listening", () => { + console.log(`UDP Server Listening on ${port}`); + }); + this.udp.on("message", (message, remote) => { + let request = new request_1.Request(message, (data) => { + // console.log("sending:", new Request(data, (a) => 0)); + this.udp.send(data, remote.port, remote.address); + }); + onRequest(request); + }); + this.udp.bind(port, host); + break; + case "tcp": + console.log("Not correct implemented"); + this.tcp = net.createServer((socket) => { + let length; + let got = 0; + let message = undefined; + socket.on("data", (data) => { + let offset = 0; + if (!message) { + length = data.readUInt16BE(0); + if (length > 2048) + return socket.destroy(); //Requests with more that 2k are ignored + message = Buffer.alloc(length); + offset = 2; + } + let read = (data.length - offset) > (length - got) ? (length - got) : (data.length - offset); + data.copy(message, got, offset, read + offset); + got += read; + //ToDo don't ignore probably following requests + if (got >= length) { + let request = new request_1.Request(message, (data) => { + socket.write(data); + }); + got = 0; + message = undefined; + length = 0; + onRequest(request); + } + }); + }); + this.tcp.listen(port, host); + break; + default: + throw new Error("Unknown socket type"); + } + } + close() { + if (this.udp) { + this.udp.close(); + } + else { + this.tcp.close(); + } + } +} +exports.default = Listener; +//# sourceMappingURL=listener.js.map \ No newline at end of file diff --git a/lib/listener.js.map b/lib/listener.js.map new file mode 100644 index 0000000..2320a9e --- /dev/null +++ b/lib/listener.js.map @@ -0,0 +1 @@ +{"version":3,"file":"listener.js","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/request.d.ts b/lib/request.d.ts new file mode 100644 index 0000000..6b0af5c --- /dev/null +++ b/lib/request.d.ts @@ -0,0 +1,120 @@ +/// +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 declare class Request implements Message { + private sendCallback; + _header: MessageHeader; + readonly header: MessageHeader; + _questions: MessageQuestion[]; + readonly questions: MessageQuestion[]; + answers: RecourceRecord[]; + authorities: RecourceRecord[]; + additionals: RecourceRecord[]; + _packet: Buffer; + constructor(packet: Buffer, sendCallback: (packet: Buffer) => any); + send(): void; + private serialize(truncate?, rcode?); + private serializeHeader(); + private serializeQuestion(question); + private serializeResourceRecord(record); + private serializeName(name); +} +export declare class RecourceRecord implements MessageRecourceRecord { + NAME: string; + private _TYPE; + TYPE: number; + private _CLASS; + CLASS: number; + _TTL: number; + TTL: number; + RDATA: Buffer; + readonly RDLENGTH: number; +} diff --git a/lib/request.js b/lib/request.js new file mode 100644 index 0000000..41a78e3 --- /dev/null +++ b/lib/request.js @@ -0,0 +1,309 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const binary_parser_1 = require("binary-parser"); +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() + .endianess("big") + .uint16("ID") + .bit1("QR") + .bit4("OPCODE") + .bit1("AA") + .bit1("TC") + .bit1("RD") + .bit1("RA") + .bit1("Z") + .bit1("AD") + .bit1("CD") + .bit4("RCODE") + .uint16("QDCOUNT") + .uint16("ANCOUNT") + .uint16("NSCOUNT") + .uint16("ARCOUNT"); +const labelParser = new binary_parser_1.Parser() + .endianess("big") + .uint8("dataLength") + .string("name", { + length: "dataLength", + encoding: "ascii" +}); +const questionParser = new binary_parser_1.Parser() + .endianess("big") + .array("QNAME", { + type: labelParser, + readUntil: (item, buffer) => { + if (item.dataLength <= 0) + return true; + }, + formatter: (value) => { + return value.map(e => e.name).join(".").slice(0, -1); + } +}) + .uint16("QTYPE") + .uint16("QCLASS"); +function parseQuestions(count, packet) { + return new binary_parser_1.Parser() + .endianess("big") + .array("questions", { + type: questionParser, + length: count + }).parse(packet).questions; +} +class Request { + constructor(packet, sendCallback) { + this.sendCallback = sendCallback; + this.answers = []; + this.authorities = []; + this.additionals = []; + this._packet = packet; + 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 = headerParser.parse(headerData); + this._header.AD = 0; + this._questions = parseQuestions(this._header.QDCOUNT, bodyData); + } + get header() { + return Object.assign({}, this._header); + } + get questions() { + return this._questions.map(e => e); + } + send() { + this.sendCallback(this.serialize()); + } + serialize(truncate = false, rcode = 0) { + 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; + this._header.RA = 0; + 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 questionsByteLength = 0; + questions.forEach(e => questionsByteLength += e.length); + let answersByteLength = 0; + answers.forEach(e => answersByteLength += e.length); + let authorityByteLength = 0; + 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 (truncate && length > 512) { + this._header.TC = 1; + //Buffer will ignore data that exeeds the max buffer length + length = 512; + } + let header = this.serializeHeader(); + let data = Buffer.alloc(length); + let offset = 0; + let append = (buffer) => { + buffer.copy(data, offset, 0, buffer.length); + offset += buffer.length; + }; + append(header); + questions.forEach(append); + answers.forEach(append); + authority.forEach(append); + additional.forEach(append); + return data; + } + 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); + data.writeUInt16BE(header.QDCOUNT, 4); + data.writeUInt16BE(header.ANCOUNT, 6); + data.writeUInt16BE(header.NSCOUNT, 8); + data.writeUInt16BE(header.ARCOUNT, 10); + return data; + } + serializeQuestion(question) { + let qname = this.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 = this.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; + } + serializeName(name) { + let length = 0; + let parts = name.split("."); + parts.forEach(e => { + // Length of part and byte that holds the length information + if (e.length > MAX_LABEL_SIZE) + throw new Error("Label to large"); + length += e.length + 1; + }); + length += 1; //Adding last 0 length octet + let data = Buffer.alloc(length); + let offset = 0; + parts.forEach(e => { + console.log(e.length); + data.writeUInt8(e.length, offset); + offset++; + data.write(e, offset, e.length); + offset += e.length; + }); + data.writeUInt8(0, offset); + console.log("name |", data.toString("hex"), length); + return data; + } +} +exports.Request = Request; +class RecourceRecord { + set TYPE(value) { + if (value < 0 || value > 65535) + throw new TypeError("TYPE Range: 0 - 65.535"); + this._TYPE = value; + } + get TYPE() { + return this._TYPE; + } + set CLASS(value) { + if (value < 0 || value > 65535) + throw new TypeError("CLASS Range: 0 - 65.535"); + this._CLASS = value; + } + get CLASS() { + return this._CLASS; + } + set TTL(value) { + if (value < 0 || value > 4294967295) + throw new TypeError("TTL Range: 0 - 4.294.967.295"); + } + get TTL() { + return this._TTL; + } + get RDLENGTH() { + return this.RDATA.length; + } +} +exports.RecourceRecord = RecourceRecord; +//# sourceMappingURL=request.js.map \ No newline at end of file diff --git a/lib/request.js.map b/lib/request.js.map new file mode 100644 index 0000000..992e23f --- /dev/null +++ b/lib/request.js.map @@ -0,0 +1 @@ +{"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/test.d.ts b/lib/test.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/lib/test.js b/lib/test.js new file mode 100644 index 0000000..4681d88 --- /dev/null +++ b/lib/test.js @@ -0,0 +1,6 @@ +// const t = `71 15 01 00 00 01 00 00 00 00 00 00 02 64 63 08 73 65 72 76 69 63 65 73 0c 76 69 73 75 61 6c 73 74 75 64 69 6f 03 63 6f 6d 00 00 01 00 01`.split(" ").join(""); +// // const tp = Buffer.from(t, "hex") +// // // import Parse from "./index"; +// // import * as util from "util"; +// // console.log(util.inspect(Parse(tp), false, 20, true)); +//# sourceMappingURL=test.js.map \ No newline at end of file diff --git a/lib/test.js.map b/lib/test.js.map new file mode 100644 index 0000000..62c4dee --- /dev/null +++ b/lib/test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test.js","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AAAA,6KAA6K;AAC7K,sCAAsC;AACtC,qCAAqC;AACrC,mCAAmC;AACnC,4DAA4D"} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..a83a6f8 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "nodename-core", + "version": "0.1.0", + "main": "lib/listener.js", + "types": "lib/listener.d.ts", + "author": "Fabian Stamm ", + "license": "MIT", + "scripts": { + "build": "tsc", + "watch": "tsc --watch", + "test": "nodemon lib/test.js" + }, + "devDependencies": { + "@types/binary-parser": "^1.3.0", + "@types/node": "^10.0.8", + "nodemon": "^1.17.4", + "typescript": "^2.8.3" + }, + "dependencies": { + "binary-parser": "^1.3.2" + } +} \ No newline at end of file diff --git a/src/listener.ts b/src/listener.ts new file mode 100644 index 0000000..d281298 --- /dev/null +++ b/src/listener.ts @@ -0,0 +1,70 @@ +import * as net from "net" +import * as dgram from "dgram"; +import { Request } from "./request"; +import { PassThrough } from "stream"; +export default class Listener { + private udp: dgram.Socket + private tcp: net.Server + constructor(type: "udp" | "tcp", onRequest: (request: Request) => any, port: number, host: string = "0.0.0.0") { + switch (type) { + case "udp": + this.udp = dgram.createSocket("udp4") + this.udp.on("listening", () => { + console.log(`UDP Server Listening on ${port}`) + }) + + this.udp.on("message", (message, remote) => { + let request = new Request(message, (data) => { + // console.log("sending:", new Request(data, (a) => 0)); + this.udp.send(data, remote.port, remote.address) + }) + onRequest(request); + }) + + this.udp.bind(port, host) + break; + case "tcp": + console.log("Not correct implemented") + this.tcp = net.createServer((socket) => { + let length: number; + let got: number = 0; + let message = undefined; + socket.on("data", (data) => { + let offset = 0; + if (!message) { + length = data.readUInt16BE(0); + if (length > 2048) return socket.destroy(); //Requests with more that 2k are ignored + message = Buffer.alloc(length); + offset = 2; + } + + let read = (data.length - offset) > (length - got) ? (length - got) : (data.length - offset); + data.copy(message, got, offset, read + offset) + got += read; + //ToDo don't ignore probably following requests + if (got >= length) { + let request = new Request(message, (data) => { + socket.write(data); + }) + got = 0; + message = undefined; + length = 0; + onRequest(request); + } + }); + }); + this.tcp.listen(port, host); + break; + default: + throw new Error("Unknown socket type") + } + } + + close() { + if (this.udp) { + this.udp.close(); + } else { + this.tcp.close(); + } + } +} \ No newline at end of file diff --git a/src/request.ts b/src/request.ts new file mode 100644 index 0000000..9782f53 --- /dev/null +++ b/src/request.ts @@ -0,0 +1,365 @@ +import { Parser } from "binary-parser" + +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() + .endianess("big") + .uint16("ID") + .bit1("QR") + .bit4("OPCODE") + .bit1("AA") + .bit1("TC") + .bit1("RD") + .bit1("RA") + .bit1("Z") + .bit1("AD") + .bit1("CD") + .bit4("RCODE") + .uint16("QDCOUNT") + .uint16("ANCOUNT") + .uint16("NSCOUNT") + .uint16("ARCOUNT") + +const labelParser = new Parser() + .endianess("big") + .uint8("dataLength") + .string("name", { + length: "dataLength", + encoding: "ascii" + }) + +const questionParser = new Parser() + .endianess("big") + .array("QNAME", { + type: labelParser, + readUntil: (item: any, buffer) => { + if (item.dataLength <= 0) + return true; + }, + formatter: (value: { dataLength: number, name: string }[]) => { + return value.map(e => e.name).join(".").slice(0, -1); + } + }) + .uint16("QTYPE") + .uint16("QCLASS") + +function parseQuestions(count: number, packet: Buffer): MessageQuestion[] { + return new Parser() + .endianess("big") + .array("questions", { + type: questionParser, + length: count + }).parse(packet).questions; +} + +export class Request implements Message { + _header: MessageHeader; + get header() { + return Object.assign({}, this._header); + } + + _questions: MessageQuestion[]; + get questions() { + return this._questions.map(e => e); + } + + answers: RecourceRecord[] = []; + authorities: RecourceRecord[] = []; + additionals: RecourceRecord[] = []; + + _packet: Buffer; + constructor(packet: Buffer, private sendCallback: (packet: Buffer) => any) { + this._packet = packet; + 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 = headerParser.parse(headerData); + this._header.AD = 0; + this._questions = parseQuestions(this._header.QDCOUNT, bodyData); + } + + send() { + this.sendCallback(this.serialize()); + } + + private serialize(truncate: boolean = false, rcode: 0 | 1 | 2 | 3 | 4 | 5 = 0) { + 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; + this._header.RA = 0; + 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 questionsByteLength = 0; + questions.forEach(e => questionsByteLength += e.length); + + let answersByteLength = 0; + answers.forEach(e => answersByteLength += e.length) + + let authorityByteLength = 0; + 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 (truncate && length > 512) { + this._header.TC = 1; + + //Buffer will ignore data that exeeds the max buffer length + length = 512; + } + + let header = this.serializeHeader() + + let data = Buffer.alloc(length) + let offset = 0; + + let append = (buffer: Buffer) => { + buffer.copy(data, offset, 0, buffer.length) + offset += buffer.length; + } + + append(header) + questions.forEach(append) + answers.forEach(append) + authority.forEach(append) + 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); + + data.writeUInt16BE(header.QDCOUNT, 4) + data.writeUInt16BE(header.ANCOUNT, 6) + data.writeUInt16BE(header.NSCOUNT, 8) + data.writeUInt16BE(header.ARCOUNT, 10) + return data; + } + + private serializeQuestion(question: MessageQuestion) { + let qname = this.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; + } + + private serializeResourceRecord(record: MessageRecourceRecord) { + // TODO: Implement compression + let name = this.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; + } + + private serializeName(name: string) { + let length = 0; + let parts = name.split("."); + parts.forEach(e => { + // Length of part and byte that holds the length information + if (e.length > MAX_LABEL_SIZE) throw new Error("Label to large"); + length += e.length + 1; + }) + + length += 1; //Adding last 0 length octet + let data = Buffer.alloc(length); + let offset = 0; + parts.forEach(e => { + console.log(e.length); + data.writeUInt8(e.length, offset) + offset++ + data.write(e, offset, e.length) + offset += e.length + }) + data.writeUInt8(0, offset); + console.log("name |", data.toString("hex"), length) + return data; + } +} + +export class RecourceRecord implements MessageRecourceRecord { + NAME: string + private _TYPE: number; + set TYPE(value) { + if (value < 0 || value > 65535) throw new TypeError("TYPE Range: 0 - 65.535") + this._TYPE = value; + } + + get TYPE() { + return this._TYPE; + } + + private _CLASS: number; + set CLASS(value) { + if (value < 0 || value > 65535) throw new TypeError("CLASS Range: 0 - 65.535") + this._CLASS = value; + } + + get CLASS() { + return this._CLASS; + } + + _TTL: number; + set TTL(value) { + if (value < 0 || value > 4294967295) throw new TypeError("TTL Range: 0 - 4.294.967.295") + } + + get TTL() { + return this._TTL; + } + + RDATA: Buffer; + + get RDLENGTH() { + return this.RDATA.length; + } +} \ No newline at end of file diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..bab4bfa --- /dev/null +++ b/src/test.ts @@ -0,0 +1,5 @@ +// const t = `71 15 01 00 00 01 00 00 00 00 00 00 02 64 63 08 73 65 72 76 69 63 65 73 0c 76 69 73 75 61 6c 73 74 75 64 69 6f 03 63 6f 6d 00 00 01 00 01`.split(" ").join(""); +// // const tp = Buffer.from(t, "hex") +// // // import Parse from "./index"; +// // import * as util from "util"; +// // console.log(util.inspect(Parse(tp), false, 20, true)); \ No newline at end of file diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000..6170627 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,177 @@ +interface MessageHeader { + + /** + * A 16 bit identifier assigned by the program that + * generates any kind of query. This identifier is copied + * the corresponding reply and can be used by the requester + * to match up replies to outstanding queries. + */ + ID: number + + /** + * Defines if query or response + */ + QR: 0 | 1 + + /** + * 4 Bit code, that defines type of query. + * 0 Standard query + * 1 Inverse query + * 2 Server status request + * 3-15 reserved for future use + */ + OPCODE: number + + /** + * Authorative Answer - only valid in responses and + * specifies that the responding name server is an + * authority for the domain name in question section + */ + AA: 0 | 1 + + /** + * Truncation - specifies that his message was truncated doe to + * length grater than permitted on the transaction channel + * + * WARNING: NOT IMPLEMENTED IN THIS APPLICATION + */ + TC: 0 | 1 + + /** + * Recursion Desired - set in query and copied to response + * if is set, directs name server to pursue the query recursively + * + * WARNING: NOT IMPLEMENTED IN THIS APPLICATION + */ + RD: 0 | 1 + + /** + * Recursion Available - will be cleared in response to + * show the client that recursion is not available + */ + RA: 0 | 1 + + /** + * Reserved for future usage, must be 0 in all queries + */ + Z: 0 + + AD: 0 | 1 + + CD: 0 | 1 + + + /** + * Response Code - 4 bit field is part of response + * + * 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 + */ + RCODE: 0 | 1 | 2 | 3 | 4 | 5 + + /** + * Number of entries in question section + * uint16 + */ + QDCOUNT: number + + /** + * Number of entries in answer section + * uint16 + */ + ANCOUNT: number + + /** + * Number of resource records in authority records section + * uint16 + */ + NSCOUNT: number + + /** + * Number of resource records in additional records section + * uint16 + */ + ARCOUNT: number +} + +interface MessageQuestion { + /** + * Domain name represented as sequence of labels + * Each label consists of a length octed followed + * by that number of octeds + */ + QNAME: string + + /** + * Two octed code wich specifies the type of the query. + */ + QTYPE: number + + /** + * Two octed code that specifies the class of the Query + * IS for internet + * WARNING: ONLY IN IS SUPPORTED BY THIS APPLICATION + */ + QCLASS: number +} + +interface MessageRecourceRecord { + /** + * Domain name to wich resource record pertains + */ + NAME: string + + /** + * Two octets containing TT type code. + * Specifies meaning of data in RDATA field + * + * uint16 + */ + TYPE: number + + /** + * Two octets specifying class of RDATA field + * + * uint16 + */ + CLASS: number + + /** + * Specifies Time Interval (in seconds) that the record + * may be cached before discarded. + * Zero values are not cached + * + * uint32 + */ + TTL: number + + /** + * Length of RDATA field + * + * uint16 + */ + RDLENGTH: number + + /** + * a variable length string of ectets taht describes + * the resource. The format is defined by TYPE and CLASS + * field. + * + * If TYPE is A and CLASS is IN, RDATA is a 4 octet + * ARPA internet address. + */ + RDATA: Buffer +} + +interface Message { + header: MessageHeader + questions: MessageQuestion[] + answers: MessageRecourceRecord[]; + authorities: MessageRecourceRecord[]; + additionals: MessageRecourceRecord[]; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2ff74b7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,55 @@ +{ + "compilerOptions": { + /* Basic Options */ + "target": "es2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./lib", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": false, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + //"esModuleInterop": false /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + /* Source Map Options */ + // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + }, + "include": [ + "./src" + ] +} \ No newline at end of file