231 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			231 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| '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 = {
 | |
|     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.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 = {
 | |
|     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 i;
 | |
|   var rec;
 | |
| 
 | |
|   //console.log('datalen', data.length);
 | |
|   console.log(dv, qdcount);
 | |
| 
 | |
|   // TODO move to pdns.unpackQuestion to make testable
 | |
|   function unpackQuestion(ab, dv, total) {
 | |
|     var ototal = total;
 | |
|     var data = new Uint8Array(ab).slice(total);
 | |
|     var 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);
 | |
|     q.qtotal = total - ototal;
 | |
|     return q;
 | |
|   }
 | |
| 
 | |
|   function unpackAnswer(ab, dv, total) {
 | |
|     var ototal = total;
 | |
|     var data = new Uint8Array(ab).slice(total);
 | |
|     var 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
 | |
|     console.log(ab.byteLength, ab.byteLength - total, ab.byteLength + -total + q.rdlength);
 | |
|     q.rdata = new Uint8Array(ab).slice(total, total + q.rdlength);
 | |
|     console.log('q.rdata', q.rdata.byteLength);
 | |
|     console.log(q.rdata);
 | |
|     q.rdata = Array.prototype.slice.apply(q.data);
 | |
|     console.log('total', total);
 | |
| 
 | |
|     total += q.rdlength;
 | |
|     console.log('total', total);
 | |
| 
 | |
|     q.qtotal = total - ototal;
 | |
|     return q;
 | |
|   }
 | |
| 
 | |
|   header.id = id;
 | |
| 
 | |
|   console.log('qdcount', qdcount);
 | |
|   header.questions = [];
 | |
|   for (i = 0; i < qdcount; i += 1) {
 | |
|     rec = unpackQuestion(ab, dv, total);
 | |
|     total += rec.qtotal;
 | |
|     header.questions.push(rec);
 | |
|   }
 | |
| 
 | |
|   console.log('ancount', ancount);
 | |
|   header.answers = [];
 | |
|   for (i = 0; i < ancount; i += 1) {
 | |
|     rec = unpackAnswer(ab, dv, total);
 | |
|     total += rec.qtotal;
 | |
|     header.answers.push(rec);
 | |
|   }
 | |
| 
 | |
|   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);
 | |
|   }
 | |
| 
 | |
|   header.total = total;
 | |
|   return header;
 | |
| };
 |