First basic implementation of protocol.

This commit is contained in:
Fabian Stamm
2018-05-13 12:07:43 +02:00
commit 524f246398
16 changed files with 1212 additions and 0 deletions

7
lib/listener.d.ts vendored Normal file
View File

@ -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;
}

69
lib/listener.js Normal file
View File

@ -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

1
lib/listener.js.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"listener.js","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":";;AAAA,2BAA0B;AAC1B,+BAA+B;AAC/B,uCAAoC;AAEpC;IAGG,YAAY,IAAmB,EAAE,SAAoC,EAAE,IAAY,EAAE,OAAe,SAAS;QAC1G,QAAQ,IAAI,EAAE;YACX,KAAK,KAAK;gBACP,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;gBACrC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;oBAC3B,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,EAAE,CAAC,CAAA;gBACjD,CAAC,CAAC,CAAA;gBAEF,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;oBACxC,IAAI,OAAO,GAAG,IAAI,iBAAO,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;wBACzC,wDAAwD;wBACxD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;oBACnD,CAAC,CAAC,CAAA;oBACF,SAAS,CAAC,OAAO,CAAC,CAAC;gBACtB,CAAC,CAAC,CAAA;gBAEF,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;gBACzB,MAAM;YACT,KAAK,KAAK;gBACP,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAA;gBACtC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,MAAM,EAAE,EAAE;oBACpC,IAAI,MAAc,CAAC;oBACnB,IAAI,GAAG,GAAW,CAAC,CAAC;oBACpB,IAAI,OAAO,GAAG,SAAS,CAAC;oBACxB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;wBACxB,IAAI,MAAM,GAAG,CAAC,CAAC;wBACf,IAAI,CAAC,OAAO,EAAE;4BACX,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;4BAC9B,IAAI,MAAM,GAAG,IAAI;gCAAE,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,wCAAwC;4BACpF,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;4BAC/B,MAAM,GAAG,CAAC,CAAC;yBACb;wBAED,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;wBAC7F,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC,CAAA;wBAC9C,GAAG,IAAI,IAAI,CAAC;wBACZ,+CAA+C;wBAC/C,IAAI,GAAG,IAAI,MAAM,EAAE;4BAChB,IAAI,OAAO,GAAG,IAAI,iBAAO,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gCACzC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;4BACtB,CAAC,CAAC,CAAA;4BACF,GAAG,GAAG,CAAC,CAAC;4BACR,OAAO,GAAG,SAAS,CAAC;4BACpB,MAAM,GAAG,CAAC,CAAC;4BACX,SAAS,CAAC,OAAO,CAAC,CAAC;yBACrB;oBACJ,CAAC,CAAC,CAAC;gBACN,CAAC,CAAC,CAAC;gBACH,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC5B,MAAM;YACT;gBACG,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAA;SAC3C;IACJ,CAAC;IAED,KAAK;QACF,IAAI,IAAI,CAAC,GAAG,EAAE;YACX,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;SACnB;aAAM;YACJ,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;SACnB;IACJ,CAAC;CACH;AAjED,2BAiEC"}

120
lib/request.d.ts vendored Normal file
View File

@ -0,0 +1,120 @@
/// <reference types="node" />
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;
}

309
lib/request.js Normal file
View File

@ -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

1
lib/request.js.map Normal file

File diff suppressed because one or more lines are too long

0
lib/test.d.ts vendored Normal file
View File

6
lib/test.js Normal file
View File

@ -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

1
lib/test.js.map Normal file
View File

@ -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"}