dns-suite.js/pure-parser.js

217 lines
6.1 KiB
JavaScript
Raw Normal View History

2017-01-21 10:15:57 +00:00
'use strict';
var pdns = module.exports;
// 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 = {
2017-01-21 10:38:25 +00:00
id: 0 // added here to preserve console.log order
, qr: (i & 0x8000) >> 15 // Query Response (0 is question, 1 is response)
2017-01-21 10:15:57 +00:00
, opcode: (i & 0x7800) >> 11 // 0 is question
, aa: (i & 0x400) >> 10 // Authoritative Answer (response-only)
2017-01-21 17:45:45 +00:00
, 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)
2017-01-21 10:15:57 +00:00
};
return header;
};
pdns.unpackQname = function (ui8) {
var total = 0;
var i;
var len;
var q = {
2017-01-21 10:38:25 +00:00
name: ''
2017-01-21 10:15:57 +00:00
, type: 0
, class: 0
};
var str = [];
do {
len = ui8[total];
if (0xc0 === len) {
throw new Error("discovered a compression pointer at byte " + total
+ " pointing to byte " + ui8[total + 1]
+ ", but compression pointer support is not yet implemented"
);
}
2017-01-21 10:15:57 +00:00
//str.length = 0; // fast empty array
if (ui8.byteLength - total < len) {
throw new Error(
"Expected a string of length " + len
+ " but packet itself has " + (ui8.byteLength - total) + " bytes remaining"
);
}
for (i = 0; i < len; i += 1) {
total += 1;
// TODO check url-allowable characters
str.push(String.fromCharCode(ui8[total]));
}
total += 1;
if (ui8[total]) {
// pushd connecting '.', but not trailing
str.push('.');
}
//console.log('total', total, ui8[total], String.fromCharCode(ui8[total]));
} while (len);
//str.pop(); // remove trailing '.'
q.name = str.join('');
return q;
};
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);
// 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);
var header = pdns.unpackHeader(dv.getUint16(2));
var qdcount = dv.getUint16(4); // query count
var ancount = dv.getUint16(6); // answer count
var nscount = dv.getUint16(8); // authority count
var arcount = dv.getUint16(10); // additional count
var total = 12;
var i;
2017-01-21 17:45:45 +00:00
var rec;
2017-01-21 10:15:57 +00:00
// TODO move to pdns.unpackQuestion to make testable
2017-01-21 17:45:45 +00:00
function unpackQuestion(ab, dv, total) {
var ototal = total;
var data = new Uint8Array(ab).slice(total);
var q = pdns.unpackQname(data);
2017-01-21 10:15:57 +00:00
total += q.name.length + 2; // account for leading and trailing string length byte
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);
total += 2;
q.class = dv.getUint16(total);
total += 2;
2017-01-21 17:45:45 +00:00
q.qtotal = total - ototal;
return q;
2017-01-21 10:15:57 +00:00
}
2017-01-21 17:45:45 +00:00
function unpackAnswer(ab, dv, total) {
var ototal = total;
var data = new Uint8Array(ab).slice(total);
var q = pdns.unpackQname(data);
2017-01-21 10:15:57 +00:00
total += q.name.length + 2; // account for leading and trailing string length byte
if (ab.byteLength - total < 10) {
// console.error(str.join(''));
throw 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"
);
}
q.type = dv.getUint16(total);
total += 2;
q.class = dv.getUint16(total);
total += 2;
q.ttl = dv.getUint32(total);
total += 4;
q.rdlength = dv.getUint16(total);
total += 2;
2017-01-21 18:02:31 +00:00
// TODO actually parse RDATA
2017-01-21 10:32:50 +00:00
console.log(ab.byteLength, ab.byteLength - total, ab.byteLength + -total + q.rdlength);
q.rdata = new Uint8Array(ab).slice(total, total + q.rdlength);
2017-01-21 18:13:42 +00:00
console.log('q.rdata', q.rdata.byteLength, 'bytes:');
q.rdata = Array.prototype.slice.apply(q.rdata);
2017-01-21 10:32:50 +00:00
console.log(q.rdata);
2017-01-21 10:15:57 +00:00
console.log('total', total);
total += q.rdlength;
console.log('total', total);
2017-01-21 17:45:45 +00:00
q.qtotal = total - ototal;
return q;
2017-01-21 10:15:57 +00:00
}
2017-01-21 10:38:25 +00:00
header.id = id;
2017-01-21 10:15:57 +00:00
console.log('qdcount', qdcount);
header.questions = [];
for (i = 0; i < qdcount; i += 1) {
2017-01-21 17:45:45 +00:00
rec = unpackQuestion(ab, dv, total);
total += rec.qtotal;
header.questions.push(rec);
2017-01-21 10:15:57 +00:00
}
console.log('ancount', ancount);
header.answers = [];
for (i = 0; i < ancount; i += 1) {
2017-01-21 17:45:45 +00:00
rec = unpackAnswer(ab, dv, total);
total += rec.qtotal;
header.answers.push(rec);
2017-01-21 10:15:57 +00:00
}
console.log('nscount', nscount);
header.authority = [];
for (i = 0; i < nscount; i += 1) {
2017-01-21 18:13:42 +00:00
rec = unpackAnswer(ab, dv, total);
total += rec.qtotal;
header.authority.push(rec);
2017-01-21 10:15:57 +00:00
}
console.log('arcount', arcount);
header.additional = [];
for (i = 0; i < arcount; i += 1) {
2017-01-21 18:13:42 +00:00
rec = unpackAnswer(ab, dv, total);
total += rec.qtotal;
header.additional.push(rec);
2017-01-21 10:15:57 +00:00
}
2017-01-21 18:02:31 +00:00
if (ab.byteLength !== total) {
throw new Error(
"Parsed " + total + " bytes, but packet is " + ab.byteLength + " bytes."
);
}
2017-01-21 17:45:45 +00:00
header.total = total;
return header;
2017-01-21 10:15:57 +00:00
};
2017-01-21 21:33:51 +00:00
pdns.unpackRdata = require('./dns.rdata.parse.js').DNS_RDATA_PARSE;