236 lines
6.3 KiB
JavaScript
236 lines
6.3 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
// pass a terminal arg
|
||
|
var filename = process.argv[2];
|
||
|
if (!filename) {
|
||
|
console.error("Usage: node aj-listener.js <type> [count]");
|
||
|
console.error("Example: node aj-listener.js _service 0");
|
||
|
process.exit(1);
|
||
|
}
|
||
|
|
||
|
|
||
|
var PromiseA = require('bluebird');
|
||
|
var fs = PromiseA.promisifyAll(require('fs'));
|
||
|
|
||
|
fs.readFileAsync(filename, null).then(function (nb) {
|
||
|
// nb is a Uint8Array (ArrayBufferView) for nb.buffer
|
||
|
// nb.buffer is the actual ArrayBuffer
|
||
|
pdns.unpack(nb.buffer);
|
||
|
});
|
||
|
|
||
|
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 = {
|
||
|
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.packHeader = function(h) {
|
||
|
var val = 0;
|
||
|
|
||
|
val += (h.qr << 15) & 0x8000;
|
||
|
val += (h.opcode << 11) & 0x7800;
|
||
|
val += (h.aa << 10) & 0x400;
|
||
|
val += (h.tc << 9) & 0x200;
|
||
|
val += (h.rd << 8) & 0x100;
|
||
|
val += (h.ra << 7) & 0x80;
|
||
|
val += (h.res1 << 6) & 0x40;
|
||
|
val += (h.res2 << 5) & 0x20;
|
||
|
val += (h.res3 << 4) & 0x10;
|
||
|
val += h.rcode & 0xF;
|
||
|
|
||
|
return val;
|
||
|
};
|
||
|
|
||
|
pdns.unpackQname = function (ui8) {
|
||
|
var total = 0;
|
||
|
var i;
|
||
|
|
||
|
var len;
|
||
|
var q = {
|
||
|
byteLength: 0
|
||
|
, name: ''
|
||
|
, type: 0
|
||
|
, class: 0
|
||
|
};
|
||
|
var str = [];
|
||
|
|
||
|
do {
|
||
|
len = ui8[total];
|
||
|
//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 data;
|
||
|
var i;
|
||
|
var q;
|
||
|
|
||
|
//console.log('datalen', data.length);
|
||
|
console.log(dv, qdcount);
|
||
|
|
||
|
// TODO move to pdns.unpackQuestion to make testable
|
||
|
function unpackQuestion() {
|
||
|
data = new Uint8Array(ab).slice(total);
|
||
|
q = pdns.unpackQname(data);
|
||
|
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);
|
||
|
console.log('type', q.type, total);
|
||
|
total += 2;
|
||
|
q.class = dv.getUint16(total);
|
||
|
console.log('class', q.class, total);
|
||
|
total += 2;
|
||
|
console.log('total', total);
|
||
|
|
||
|
header.questions.push(q);
|
||
|
}
|
||
|
|
||
|
function unpackAnswer(answers) {
|
||
|
data = new Uint8Array(ab).slice(total);
|
||
|
q = pdns.unpackQname(data);
|
||
|
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);
|
||
|
console.log('type', q.type, total);
|
||
|
|
||
|
total += 2;
|
||
|
q.class = dv.getUint16(total);
|
||
|
console.log('class', q.class, total);
|
||
|
|
||
|
total += 2;
|
||
|
q.ttl = dv.getUint32(total);
|
||
|
console.log('ttl', q.ttl, total);
|
||
|
|
||
|
total += 4;
|
||
|
q.rdlength = dv.getUint16(total);
|
||
|
console.log('rdlength', q.rdlength, total);
|
||
|
|
||
|
total += 2;
|
||
|
// TODO actually parse rdata
|
||
|
q.rdata = new Uint8Array(ab).slice(ab.byteLength - total, ab.byteLength + -total + q.rdlength);
|
||
|
q.rdata = Array.prototype.slice(q.data);
|
||
|
console.log('total', total);
|
||
|
|
||
|
total += q.rdlength;
|
||
|
console.log('total', total);
|
||
|
|
||
|
answers.push(q);
|
||
|
}
|
||
|
|
||
|
console.log('qdcount', qdcount);
|
||
|
header.questions = [];
|
||
|
for (i = 0; i < qdcount; i += 1) {
|
||
|
unpackQuestion();
|
||
|
}
|
||
|
|
||
|
console.log('ancount', ancount);
|
||
|
header.answers = [];
|
||
|
for (i = 0; i < ancount; i += 1) {
|
||
|
unpackAnswer(header.answers);
|
||
|
}
|
||
|
|
||
|
console.log('nscount', nscount);
|
||
|
header.authority = [];
|
||
|
for (i = 0; i < nscount; i += 1) {
|
||
|
unpackAnswer(header.authority);
|
||
|
}
|
||
|
|
||
|
console.log('arcount', arcount);
|
||
|
header.additional = [];
|
||
|
for (i = 0; i < arcount; i += 1) {
|
||
|
unpackAnswer(header.additional);
|
||
|
}
|
||
|
|
||
|
console.log('packet', header);
|
||
|
};
|