'use strict'; // pass a terminal arg var filename = process.argv[2]; if (!filename) { console.error("Usage: node aj-listener.js [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); };