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

70
src/listener.ts Normal file
View File

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

365
src/request.ts Normal file
View File

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

5
src/test.ts Normal file
View File

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

177
src/types.d.ts vendored Normal file
View File

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