354 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			354 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| var debug = require('debug')('mdns-packet:lib:dns:dnsrecord');
 | |
| 
 | |
| var BufferConsumer = require('./bufferconsumer');
 | |
| var errors = require('./errors');
 | |
| 
 | |
| /**
 | |
|  * DNSRecord is a record inside a DNS packet; e.g. a QUESTION, or an ANSWER,
 | |
|  * AUTHORITY, or ADDITIONAL record. Note that QUESTION records are special,
 | |
|  * and do not have ttl or data.
 | |
|  * @class
 | |
|  * @param {string} name
 | |
|  * @param {number} type
 | |
|  * @param {number} cl - class
 | |
|  * @param {number} [optTTL] - time to live in seconds
 | |
|  */
 | |
| var DNSRecord = module.exports = function (name, type, cl, optTTL) {
 | |
|   var self = this;
 | |
|   this.name = name;
 | |
|   this.type = type;
 | |
|   this.class = cl;
 | |
| 
 | |
|   if (type === 0 || typeof type === 'undefined') {
 | |
|     throw new errors.MalformedPacket('Record.type is empty');
 | |
|   }
 | |
| 
 | |
|   if (cl === 0) {
 | |
|     throw new errors.MalformedPacket('Record.class is empty');
 | |
|   }
 | |
| 
 | |
|   this.ttl = (typeof optTTL !== 'undefined') ? optTTL : DNSRecord.TTL;
 | |
|   this.isQD = (arguments.length === 3);
 | |
|   debug('new DNSRecord', this);
 | |
| 
 | |
| 
 | |
|   this.__defineGetter__('typeName', function () {
 | |
|     return DNSRecord.TypeName[self.type];
 | |
|   });
 | |
| 
 | |
|   this.__defineGetter__('className', function () {
 | |
|     return DNSRecord.ClassName[self.class & 0x7fff];
 | |
|   });
 | |
| 
 | |
|   this.__defineGetter__('flag', function () {
 | |
|     return (self.class & 0x8000) >> 15;
 | |
|   });
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Enum for record type values
 | |
|  * @readonly
 | |
|  * @enum {number}
 | |
|  */
 | |
| DNSRecord.Type = {
 | |
|   A: 0x01,        // 1
 | |
|   NS: 0x02,       //2
 | |
|   CNAME: 0x05,    //5
 | |
|   SOA: 0x06,     //6
 | |
|   PTR: 0x0c,      // 12
 | |
|   MX: 0x0f,       //15
 | |
|   TXT: 0x10,      // 16
 | |
|   AAAA: 28,       // 0x16
 | |
|   SRV: 0x21,       // 33
 | |
|   OPT: 0x29,      //41 RFC6981 -needed for EDNS
 | |
|   NSEC: 0x2f,    //47
 | |
|   TLSA: 0x34,      //52 RFC6698 - associate TLS server certificate.
 | |
|   ANY: 0xff
 | |
| };
 | |
| 
 | |
| /**
 | |
| * Enum for record class values
 | |
| * @readonly
 | |
| * @enum {number}
 | |
| */
 | |
| DNSRecord.Class = {
 | |
|   IN: 0x01,
 | |
|   ANY: 0xff,
 | |
|   FLUSH: 0x8000,
 | |
|   IS_QM: 0x8000
 | |
| };
 | |
| 
 | |
| 
 | |
| DNSRecord.TTL = 3600; // one hour default TTL
 | |
| DNSRecord.TypeName = {};
 | |
| DNSRecord.ClassName = {};
 | |
| 
 | |
| var typekey;
 | |
| 
 | |
| for (typekey in DNSRecord.Type) {
 | |
|   if (DNSRecord.Type.hasOwnProperty(typekey)) {
 | |
|     DNSRecord.TypeName[DNSRecord.Type[typekey]] = typekey;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| for (typekey in DNSRecord.Class) {
 | |
|   if (DNSRecord.Class.hasOwnProperty(typekey)) {
 | |
|     DNSRecord.ClassName[DNSRecord.Class[typekey]] = typekey;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| DNSRecord.write = function (out, rec, withLength) {
 | |
|   withLength = withLength || false;
 | |
|   debug('#write() type: %s, flag:%d class:%s, withLength:%s',
 | |
|     rec.typeName, rec.flag,
 | |
|     rec.className, withLength);
 | |
|   if (rec.type === 0 || rec.class === 0) {
 | |
|     throw new Error('Bad record with empty type and/or class');
 | |
|   }
 | |
|   //TODO:if outer and any string in data or name
 | |
|   //     can be found there. Do a ref instead.
 | |
|   out.name(rec.name).short(rec.type).short(rec.class);
 | |
|   if (rec.isQD) {
 | |
|     return out;
 | |
|   }
 | |
| 
 | |
|   out.long(rec.ttl);
 | |
| 
 | |
|   var startPos = out.tell();
 | |
|   out.short(0xffff); //reserve some length
 | |
| 
 | |
|   switch (rec.type) {
 | |
|     case DNSRecord.Type.A:
 | |
|       writeA(out, rec.address);
 | |
|       break;
 | |
|     case DNSRecord.Type.NS:
 | |
|     case DNSRecord.Type.CNAME:
 | |
|     case DNSRecord.Type.PTR:
 | |
|       out.name(rec.data);
 | |
|       break;
 | |
|     case DNSRecord.Type.MX:
 | |
|       //asMx(consumer, rec);
 | |
|       break;
 | |
|     case DNSRecord.Type.TXT:
 | |
|       for (var key in rec.data) {
 | |
|         if (rec.data.hasOwnProperty(key)) {
 | |
|           // properly encode this
 | |
|           out.name(key + '=' + rec.data[key]);
 | |
|           out.offset--;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|     case DNSRecord.Type.AAAA:
 | |
|       //asAAAA(consumer, rec);
 | |
|       break;
 | |
|     case DNSRecord.Type.SRV:
 | |
|       out.short(rec.priority & 0xffff).short(rec.weight & 0xffff)
 | |
|         .short(rec.port & 0xffff).name(rec.target);
 | |
|       break;
 | |
|     case DNSRecord.Type.SOA:
 | |
|       out.name(rec.primary).name(rec.admin).long(rec.serial).long(rec.refresh)
 | |
|         .long(rec.retry).long(rec.expiration).long(rec.minimum);
 | |
|       break;
 | |
|     default:
 | |
|       debug('non implemented recordtype of ' + rec.type);
 | |
|       throw new Error('Not implemented recordtype');
 | |
|       //this.data = new BufferConsumer(consumer.slice(dataSize));
 | |
|   }
 | |
|   var endPos = out.tell();
 | |
|   //update with correct size
 | |
|   var correctSize = endPos - startPos - 2;
 | |
|   debug('correct size=%s bytes', correctSize);
 | |
|   out.seek(startPos).short(correctSize).seek(endPos);
 | |
| 
 | |
|   return out;
 | |
| };
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| function writeA(out, ip) {
 | |
|   var parts = ip.split('.');
 | |
|   for (var i = 0; i < 4; i++) {
 | |
|     out.byte(parts[i]);
 | |
|   }
 | |
| }
 | |
| 
 | |
| DNSRecord.parse = function (consumer) {
 | |
|   if (consumer instanceof Buffer) {
 | |
|     debug('making consumer out of buffer');
 | |
|     consumer = new BufferConsumer(consumer);
 | |
|     consumer.seek(0);
 | |
|   }
 | |
| 
 | |
|   debug('#parse from %d', consumer.tell());
 | |
|   var rec = new DNSRecord(
 | |
|     consumer.name(),
 | |
|     consumer.short(), // type
 | |
|     consumer.short(), // class
 | |
|     consumer.long() //ttlgf
 | |
|   );
 | |
| 
 | |
|   debug('parsing from %d', consumer.tell());
 | |
| 
 | |
|   var dataSize = consumer.short();
 | |
|   debug('going for type %s. start: %d, size: %d, end: %d, length: %d',
 | |
|     rec.type,
 | |
|     consumer.tell(),
 | |
|     dataSize,
 | |
|     consumer.tell() + dataSize,
 | |
|     consumer.length
 | |
|   );
 | |
| 
 | |
| 
 | |
|   switch (rec.type) {
 | |
|     case DNSRecord.Type.A:
 | |
|       asA(consumer, rec);
 | |
|       break;
 | |
|     case DNSRecord.Type.NS:
 | |
|     case DNSRecord.Type.CNAME:
 | |
|     case DNSRecord.Type.PTR:
 | |
|       rec.data = asName(consumer);
 | |
|       break;
 | |
|     case DNSRecord.Type.MX:
 | |
|       asMx(consumer, rec);
 | |
|       break;
 | |
|     case DNSRecord.Type.TXT:
 | |
|       rec.data = asTxt(consumer, consumer.tell() + dataSize);
 | |
|       break;
 | |
|     case DNSRecord.Type.AAAA:
 | |
|       asAAAA(consumer, rec);
 | |
|       break;
 | |
|     case DNSRecord.Type.SRV:
 | |
|       asSrv(consumer, rec);
 | |
|       break;
 | |
|     case DNSRecord.Type.SOA:
 | |
|       asSoa(consumer, rec);
 | |
|       break;
 | |
|     case DNSRecord.Type.OPT:
 | |
|       asOpt(consumer, rec);
 | |
|       break;
 | |
|     case DNSRecord.Type.TLSA:
 | |
|       asTLSA(consumer, rec, dataSize);
 | |
|       break;
 | |
|     default:
 | |
|       debug('non implemented recordtype of ' + rec.type);
 | |
|       rec.data = new BufferConsumer(consumer.slice(dataSize));
 | |
|   }
 | |
|   debug('record done at %d', consumer.tell(), rec);
 | |
|   return rec;
 | |
| };
 | |
| 
 | |
| DNSRecord.parseQuestion = function (consumer) {
 | |
|   if (consumer instanceof Buffer) {
 | |
|     debug('making consumer out of buffer');
 | |
|     consumer = new BufferConsumer(consumer);
 | |
|   }
 | |
|   debug('#parseQuestion from %d', consumer.tell());
 | |
|   var r = new DNSRecord(
 | |
|           consumer.name(),
 | |
|           consumer.short(), // type
 | |
|           consumer.short() // class
 | |
|           );
 | |
|   debug('record done at %d', consumer.tell(), r);
 | |
|   return r;
 | |
| };
 | |
| 
 | |
| 
 | |
| function asName(consumer) {
 | |
|   return consumer.name(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| function asSrv(consumer, record) {
 | |
|   debug('priority: %d', record.priority = consumer.short());
 | |
|   debug('weight: %d', record.weight = consumer.short());
 | |
|   debug('port: %d', record.port = consumer.short());
 | |
|   record.target = consumer.name();
 | |
|   // debug('priority:%d, weight: %d, port:%d, target:%s', record.priority,
 | |
|   //   record.weight, record.port, record.target);
 | |
| 
 | |
| }
 | |
| 
 | |
| function asMx(consumer, record) {
 | |
|   record.priority = consumer.short();
 | |
|   record.exchange = asName(consumer);
 | |
| }
 | |
| 
 | |
| function asTxt(consumer, endAt) {
 | |
|   var items = consumer.name(false, endAt);
 | |
|   debug('txt items', items);
 | |
|   //note:disable to have same syntax as native-dns-packet
 | |
|   // if (items.length === 1 && items[0].length > 0) {
 | |
|   //   return items[0];
 | |
|   // }
 | |
|   return items;
 | |
| }
 | |
| 
 | |
| 
 | |
| function asA(consumer, record) {
 | |
|   var data = '';
 | |
|   for (var i = 0; i < 3; i++) {
 | |
|     data += consumer.byte() + '.';
 | |
|   }
 | |
|   data += consumer.byte();
 | |
|   record.address = data;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Parse data into a IPV6 address string
 | |
|  * @returns {string}
 | |
|  */
 | |
| function asAAAA(consumer, packet) {
 | |
|   var data = '';
 | |
|   for (var i = 0; i < 7; i++) {
 | |
|     data += consumer.short().toString(16) + ':';
 | |
|   }
 | |
|   data += consumer.short().toString(16);
 | |
|   packet.address = data;
 | |
| }
 | |
| 
 | |
| function asSoa(consumer, packet) {
 | |
|   packet.primary = consumer.name(true);
 | |
|   packet.admin = consumer.name(true);
 | |
|   packet.serial = consumer.long();
 | |
|   packet.refresh = consumer.long();
 | |
|   packet.retry = consumer.long();
 | |
|   packet.expiration = consumer.long();
 | |
|   packet.minimum = consumer.long();
 | |
| }
 | |
| 
 | |
| function asOpt(consumer, packet) {
 | |
|   //if at end of buffer there is no optional data.
 | |
|   var opt = {
 | |
|     code: 0,
 | |
|     data: [],
 | |
|     rcode: 0,
 | |
|     version: 0,
 | |
|     do: 0,
 | |
|     z: 0
 | |
|   };
 | |
| 
 | |
|   if (!consumer.isEOF()) {
 | |
|     opt.code = consumer.short();
 | |
|     opt.data = consumer.slice(consumer.short());
 | |
|   }
 | |
| 
 | |
|   opt.rcode = (packet.ttl & 0xff000000) >> 24;
 | |
|   opt.version = (packet.ttl & 0x00FF0000) >> 16;
 | |
|   opt.do = (packet.ttl & 0x00008000) >> 15;
 | |
|   opt.z = (packet.ttl & 0x00001FFF);
 | |
| 
 | |
|   debug('asOpt', opt);
 | |
|   packet.opt = opt;
 | |
| }
 | |
| 
 | |
| function asTLSA(consumer, packet, dataSize) {
 | |
|   packet.usage = consumer.byte();
 | |
|   packet.selector = consumer.byte();
 | |
|   packet.matchingtype = consumer.byte();
 | |
|   packet.buff = consumer.slice(dataSize - 3); //size - 3 because of 3 bytes above
 | |
| }
 |