Fast, lightweight, easy-to-extend, easy-to-test, pure JavaScript (ES5.1) implementation for DNS / mDNS.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

262 lines
7.7 KiB

;(function (exports) {
'use strict';
var pdns = exports.DNS_PARSER = {};
var classes = exports.DNS_CLASSES || require('./dns.classes.js').DNS_CLASSES;
var types = exports.DNS_TYPES || require('./dns.types.js').DNS_TYPES;
// Order http://www.zytrax.com/books/dns/ch15/
pdns.unpackHeader = function (i) {
// i is one element from a Uint16Array (as a 16-bit unsigned integer)
var header = {
id: 0 // added here to preserve console.log order
, qr: (i & 0x8000) >> 15 // Query Response (0 is question, 1 is response)
, opcode: (i & 0x7800) >> 11 // 0 is question
, aa: (i & 0x400) >> 10 // Authoritative Answer (response-only)
, tc: (i & 0x200) >> 9 // TrunCated - expect another packet with same (?) id
, rd: (i & 0x100) >> 8 // Recursion Desired
, ra: (i & 0x80) >> 7
, res1: (i & 0x40) >> 6 // z
, res2: (i & 0x20) >> 5 // ad
, res3: (i & 0x10) >> 4 // cd
, rcode: (i & 0xF) // Error Code (response-only)
};
return header;
};
pdns._unpackLabels = exports.DNS_UNPACK_LABELS || require('./dns.unpack-labels.js').DNS_UNPACK_LABELS;
var optWarned = false;
pdns.unpackOpt = function (ab, packet, rec) {
var dv;
// https://tools.ietf.org/html/rfc6891#section-6
if (!optWarned) {
console.warn('OPT is not yet supported');
optWarned = true;
}
if ('undefined' !== typeof packet.edns_version) {
console.warn("More that one OPT, should respond with FORMERR, but not implemented");
}
if (packet.name) {
console.warn("name '" + packet.name + "' should not exist for type OPT 0x29 41");
}
packet.payload = rec.class;
// TODO index into the correct place in the ArrayBuffer
dv = new DataView(new ArrayBuffer(4));
// rec.ttl is actually 1 (extended_rcode), 1 (edns_version), 2:1 (DO), 2:7 (Z)
dv.setUint32(0, packet.ttl, false);
// is xrcode this edns_options?
packet.xrcode = '0x' + dv.getUint8(0, false).toString(16);
if ('0x0' === packet.xrcode) {
// use 4-bit rcode instead of 12-bit rcode
}
else {
// shift xrcode up by 4 bits (8 bits + 4 trailing 0s)
// OR with existing rcode (now 12-bits total)
console.warn('extended_rcode not supported yet');
}
packet.edns_version = dv.getUint8(1, false);
packet.do = dv.getUint8(2, false) & 0x8000; // 1000 0000
packet.z = dv.getUint16(2, false) & 0x7FFF; // 0111 1111
/*
"edns_options": [],
"payload": 4096,
"edns_version": 0,
"do": 0
*/
};
pdns.unpack = function (ab) {
if (ab.buffer) {
ab = ab.buffer;
}
// SANITY Check
if (ab.byteLength < 12) {
throw new Error(
"A DNS header has a minimum length of 12 bytes but this packet has only " + ab.byteLength + "bytes."
);
}
// DO: new Uint8Array(arrayBuffer);
// DO NOT: Uint8Array.from(arrayBuffer); // WILL NOT WORK
// DO: new DataView(arrayBuffer).getUint16(7, false);
// DO NOT: arrayBuffer.slice();
//
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
// https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView
var dv = new DataView(ab);
var id = dv.getUint16(0, false);
var packet = {};
packet.header = pdns.unpackHeader(dv.getUint16(2, false));
packet.qdcount = dv.getUint16(4, false); // query count
packet.ancount = dv.getUint16(6, false); // answer count
packet.nscount = dv.getUint16(8, false); // authority count
packet.arcount = dv.getUint16(10, false); // additional count
var total = 12;
var i;
var rec;
var ui8 = new Uint8Array(ab);
// TODO move to pdns.unpackQuestion to make testable
function unpackQuestion(ab, dv, ui8, total) {
var ototal = total;
var q = pdns._unpackLabels(ui8, total, {
name: '' // ex: daplie.com
, type: 0 // ex: 1
, typeName: '' // ex: A
, class: 0 // ex: 1
, className: '' // ex: IN
, byteLength: 0 // the total byte length (including pointers, but not their labels)
, labels: [] // an array of the labels individually
, cpcount: 0 // the number of compression pointers traversed
});
//console.log('unpackQuestion QNAME:');
//console.log(q);
total += q.byteLength;
if (ab.byteLength - total < 4) {
// console.error(str.join(''));
throw new Error(
"Expected a 2-byte QTYPE and 2-byte QCLASS following " + total + "-byte QNAME"
+ " but packet itself has " + (ab.byteLength - total) + " bytes remaining"
);
}
q.type = dv.getUint16(total, false);
total += 2;
q.class = dv.getUint16(total, false);
total += 2;
q.byteLength = total - ototal;
q.className = classes[q.class];
q.typeName = types[q.type];
return q;
}
function unpackAnswer(ab, dv, ui8, total) {
var ototal = total;
var err;
var q = pdns._unpackLabels(ui8, total, {
name: ''
, type: 0
, typeName: ''
, class: 0
, className: ''
, byteLength: 0
, labels: []
, cpcount: 0
, rdstart: 0
//, rdata: 0
, rdlength: 0
});
//console.log('unpackAnswer [Q]NAME:');
//console.log(q);
total += q.byteLength;
q.className = classes[q.class];
q.typeName = types[q.type];
if (ab.byteLength - total < 10) {
// console.error(str.join(''));
//console.error(JSON.stringify(packet, null, 1));
//console.error(JSON.stringify(q, null, 1));
err = new Error(
"Expected a 2-byte QTYPE, 2-byte QCLASS, 4-byte TTL, and 2-byte RDLENGTH following " + total + "-byte QNAME"
+ " but packet itself has " + (ab.byteLength - total) + " bytes remaining"
);
err.packet = packet;
err.record = q;
throw err;
}
q.type = dv.getUint16(total, false);
total += 2;
q.class = dv.getUint16(total, false);
total += 2;
q.ttl = dv.getUint32(total, false);
total += 4;
q.rdlength = dv.getUint16(total, false);
total += 2;
q.className = classes[q.class];
q.typeName = types[q.type];
// TODO actually parse RDATA
q.rdstart = total;
// console.log('q.rdata', q.rdlength, 'bytes:');
// console.log(new Uint8Array(ab).slice(total, total + q.rdlength));
//q.rdata = Array.prototype.slice.apply(q.rdata);
//q.rdend = q.rdstart + q.rdlength;
total += q.rdlength;
q.byteLength = total - ototal;
return q;
}
packet.header.id = id;
packet.question = [];
for (i = 0; i < packet.qdcount; i += 1) {
rec = unpackQuestion(ab, dv, ui8, total);
total += rec.byteLength;
packet.question.push(rec);
}
packet.answer = [];
for (i = 0; i < packet.ancount; i += 1) {
rec = unpackAnswer(ab, dv, ui8, total);
total += rec.byteLength;
packet.answer.push(rec);
}
packet.authority = [];
for (i = 0; i < packet.nscount; i += 1) {
rec = unpackAnswer(ab, dv, ui8, total);
total += rec.byteLength;
packet.authority.push(rec);
}
packet.edns_options = [];
packet.additional = [];
for (i = 0; i < packet.arcount; i += 1) {
rec = unpackAnswer(ab, dv, ui8, total);
total += rec.byteLength;
if (0x29 === rec.type) {
// OPT 41 (0x29)
pdns.unpackOpt(ab, packet, rec);
continue;
}
packet.additional.push(rec);
}
if (ab.byteLength !== total) {
throw new Error(
"Parsed " + total + " bytes, but packet is " + ab.byteLength + " bytes."
);
}
packet.byteLength = total;
return packet;
};
pdns.unpackRdata = exports.DNS_RDATA_PARSE || require('./dns.rdata.parse.js').DNS_RDATA_PARSE;
}('undefined' !== typeof window ? window : exports));