dns-suite.js/node_modules/dns-js/lib/dnspacket.js

262 lines
7.0 KiB
JavaScript

var debug = require('debug')('mdns-packet:lib:dns:dnspacket');
var BufferWriter = require('./bufferwriter');
var DataConsumer = require('./bufferconsumer');
var DNSRecord = require('./dnsrecord');
var errors = require('./errors');
var MIN_RECORD_SIZE = 5;
/**
* This callback is used for "each" methods
* @callback DNSPacket~eachCallback
* @param {DNSRecord} rec - DNSRecord that was found
*/
var SECTION_NAMES = [
'answer',
'authority',
'additional'
];
var ALL_SECTION_NAMES = ['question'].concat(SECTION_NAMES);
// parses through the flags
function parseFlags(val, packet) {
packet.header.qr = (val & 0x8000) >> 15;
packet.header.opcode = (val & 0x7800) >> 11;
packet.header.aa = (val & 0x400) >> 10;
packet.header.tc = (val & 0x200) >> 9;
packet.header.rd = (val & 0x100) >> 8;
packet.header.ra = (val & 0x80) >> 7;
packet.header.res1 = (val & 0x40) >> 6;
packet.header.res2 = (val & 0x20) >> 5;
packet.header.res3 = (val & 0x10) >> 4;
packet.header.rcode = (val & 0xF);
}
function parseHeader(consumer, packet) {
packet.header.id = consumer.short();
parseFlags(consumer.short(), packet);
packet.question = new Array(consumer.short());
packet.answer = new Array(consumer.short());
packet.authority = new Array(consumer.short());
packet.additional = new Array(consumer.short());
debug('packet.header:', packet.header);
debug('question: %d, answer: %d, authority: %d, additional: %d',
packet.question.length, packet.answer.length, packet.authority.length,
packet.additional.length);
var allcount = packet.question.length + packet.answer.length +
packet.authority.length + packet.additional.length;
// allcount * MIN_RECORD_SIZE should be less then consumer.length - consumer.tell()
if (allcount * MIN_RECORD_SIZE > (consumer.length - consumer.tell())) {
throw new errors.MalformedPacket(
'Unexpectedly big section count: %d. Missing at least %d bytes.',
allcount,
allcount * MIN_RECORD_SIZE - (consumer.length - consumer.tell()));
}
}
function writeHeader(writer, packet) {
var header = packet.header;
writer.short(header.id);
var val = 0;
val += (header.qr << 15) & 0x8000;
val += (header.opcode << 11) & 0x7800;
val += (header.aa << 10) & 0x400;
val += (header.tc << 9) & 0x200;
val += (header.rd << 8) & 0x100;
val += (header.ra << 7) & 0x80;
val += (header.res1 << 6) & 0x40;
val += (header.res1 << 5) & 0x20;
val += (header.res1 << 4) & 0x10;
val += header.rcode & 0xF;
writer.short(val);
}
/**
* DNSPacket holds the state of a DNS packet. It can be modified or serialized
* in-place.
*
* @constructor
*/
var DNSPacket = module.exports = function (flags) {
this.header = {
id: 0,
qr: 0,
opcode: 0,
aa: 0,
tc: 0,
rd: 1,
ra: 0,
res1: 0,
res2: 0,
res3: 0,
rcode: 0
};
if (flags) {
parseFlags(flags, this);
}
this.question = [];
this.answer = [];
this.authority = [];
this.additional = [];
this.edns_options = [];
this.payload = undefined;
};
/**
* Enum identifying DNSPacket flags
* @readonly
* @enum {number}
*/
DNSPacket.Flag = {
RESPONSE: 0x8000,
AUTHORATIVE: 0x400,
TRUNCATED: 0x200,
RECURSION: 0x100
};
/**
* Enum identifying DNSPacket rcode flag values
* @readonly
* @enum {number}
*/
DNSPacket.RCODE = {
NoError: 0,
FormErr: 1,
ServFail: 2,
NXDomain: 3
};
/**
* Parse a DNSPacket from an Buffer
* @param {Buffer} buffer - A Node.js Buffer instance
* @returns {DNSPacket} Instance of DNSPacket
*/
DNSPacket.parse = function (buffer) {
var consumer = new DataConsumer(buffer);
var packet = new DNSPacket();
var receivedOpts = 0;
parseHeader(consumer, packet);
// Parse the QUESTION section.
for (var qi = 0; qi < packet.question.length; qi++) {
debug('doing qd %s', qi);
try {
debug('before question', consumer.tell());
var part = DNSRecord.parseQuestion(consumer);
packet.question[qi] = part;
}
catch (err) {
debug('consumer', consumer);
throw err;
}
}
// Parse the ANSWER, AUTHORITY and ADDITIONAL sections.
SECTION_NAMES.forEach(function (sectionName) {
var section = packet[sectionName];
debug('about to parse section %s', sectionName, section.length);
for (var si = 0; si < section.length; si++) {
debug('doing record %s/%s', si + 1, section.length, consumer.tell());
var record = DNSRecord.parse(consumer);
debug('parsed type `%d` for section %s', record.type, sectionName);
if (record.type === DNSRecord.Type.OPT) {
if (receivedOpts++ >= 0) {
//TODO: does it only ever be in additonal.
if (sectionName === 'additional') {
packet.edns_version = record.opt.version;
packet.do = record.opt.do;
packet.payload = record.class;
}
}
else {
debug('more than 1 opts'. receivedOpts);
}
}
section[si] = record;
}
});
if (!consumer.isEOF()) {
debug('was not EOF on incoming packet. %d bytes in overflow',
consumer.length - consumer.tell());
var multiple = [packet];
multiple.push(DNSPacket.parse(consumer.slice()));
return multiple;
}
debug('packet done', packet);
return packet;
};
/**
* Get records from packet
* @param {DNSPacket.Section} section - record section [qd|an|ns|ar],
* @param {DNSRecord.Type} [filter] - DNSRecord.Type to filter on
* @param {DNSPacket~eachCallback} callback - Function callback
*/
DNSPacket.prototype.each = each;
function each(section /*[,filter], callback*/) {
if (ALL_SECTION_NAMES.indexOf(section) === -1) {
throw new Error('Unkown section, ' + section);
}
var filter = false;
var cb;
if (arguments.length === 2) {
cb = arguments[1];
}
else {
filter = arguments[1];
cb = arguments[2];
if (typeof filter === 'undefined') {
throw new Error('Filter given but is undefined');
}
}
this[section].forEach(function (rec) {
if (!filter || rec.type === filter) {
cb(rec);
}
});
}
/**
* Serialize this DNSPacket into an Buffer for sending over UDP.
* @returns {Buffer} A Node.js Buffer
*/
DNSPacket.toBuffer = function (packet) {
var writer = new BufferWriter();
var sections = ['question'].concat(SECTION_NAMES);
writeHeader(writer, packet);
sections.forEach(function (sectionName) {
var section = packet[sectionName];
debug('%d records in %s', section.length, sectionName);
writer.short(section.length);
});
var e = each.bind(packet);
sections.forEach(function (sectionName) {
e(sectionName, function (rec) {
DNSRecord.write(writer, rec, true);
if (sectionName !== 'question' && rec.isQD) {
throw new Error('unexpected QD record in non QD section.');
}
});
});
return writer.slice(0, writer.tell());
};