/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ /* jshint esnext: true, moz: true */ 'use strict'; this.EXPORTED_SYMBOLS = ['DNSPacket']; const { utils: Cu } = Components; Cu.import('resource://gre/modules/Services.jsm'); Cu.import('resource://gre/modules/DataReader.jsm'); Cu.import('resource://gre/modules/DataWriter.jsm'); Cu.import('resource://gre/modules/DNSRecord.jsm'); Cu.import('resource://gre/modules/DNSResourceRecord.jsm'); const DEBUG = true; function debug(msg) { Services.console.logStringMessage('DNSPacket: ' + msg); } let DNS_PACKET_SECTION_TYPES = [ 'QD', // Question 'AN', // Answer 'NS', // Authority 'AR' // Additional ]; /** * DNS Packet Structure * ************************************************* * * Header * ====== * * 00 2-Bytes 15 * ------------------------------------------------- * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15| * ------------------------------------------------- * |<==================== ID =====================>| * |QR|<== OP ===>|AA|TC|RD|RA|UN|AD|CD|<== RC ===>| * |<================== QDCOUNT ==================>| * |<================== ANCOUNT ==================>| * |<================== NSCOUNT ==================>| * |<================== ARCOUNT ==================>| * ------------------------------------------------- * * ID: 2-Bytes * FLAGS: 2-Bytes * - QR: 1-Bit * - OP: 4-Bits * - AA: 1-Bit * - TC: 1-Bit * - RD: 1-Bit * - RA: 1-Bit * - UN: 1-Bit * - AD: 1-Bit * - CD: 1-Bit * - RC: 4-Bits * QDCOUNT: 2-Bytes * ANCOUNT: 2-Bytes * NSCOUNT: 2-Bytes * ARCOUNT: 2-Bytes * * * Data * ==== * * 00 2-Bytes 15 * ------------------------------------------------- * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15| * ------------------------------------------------- * || * || * || * || * ------------------------------------------------- * * QD: ??-Bytes * AN: ??-Bytes * NS: ??-Bytes * AR: ??-Bytes * * * Question Record * =============== * * 00 2-Bytes 15 * ------------------------------------------------- * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15| * ------------------------------------------------- * || * |<=================== TYPE ====================>| * |<=================== CLASS ===================>| * ------------------------------------------------- * * NAME: ??-Bytes * TYPE: 2-Bytes * CLASS: 2-Bytes * * * Resource Record * =============== * * 00 4-Bytes 31 * ------------------------------------------------- * |00|02|04|06|08|10|12|14|16|18|20|22|24|26|28|30| * ------------------------------------------------- * || * |<======= TYPE ========>|<======= CLASS =======>| * |<==================== TTL ====================>| * |<====== DATALEN ======>|| * ------------------------------------------------- * * NAME: ??-Bytes * TYPE: 2-Bytes * CLASS: 2-Bytes * DATALEN: 2-Bytes * DATA: ??-Bytes (Specified By DATALEN) */ class DNSPacket { constructor() { this._flags = _valueToFlags(0x0000); this._records = {}; DNS_PACKET_SECTION_TYPES.forEach((sectionType) => { this._records[sectionType] = []; }); } static parse(data) { let reader = new DataReader(data); if (reader.getValue(2) !== 0x0000) { throw new Error('Packet must start with 0x0000'); } let packet = new DNSPacket(); packet._flags = _valueToFlags(reader.getValue(2)); let recordCounts = {}; // Parse the record counts. DNS_PACKET_SECTION_TYPES.forEach((sectionType) => { recordCounts[sectionType] = reader.getValue(2); }); // Parse the actual records. DNS_PACKET_SECTION_TYPES.forEach((sectionType) => { let recordCount = recordCounts[sectionType]; for (let i = 0; i < recordCount; i++) { if (sectionType === 'QD') { packet.addRecord(sectionType, DNSRecord.parseFromPacketReader(reader)); } else { packet.addRecord(sectionType, DNSResourceRecord.parseFromPacketReader(reader)); } } }); if (!reader.eof) { DEBUG && debug('Did not complete parsing packet data'); } return packet; } getFlag(flag) { return this._flags[flag]; } setFlag(flag, value) { this._flags[flag] = value; } addRecord(sectionType, record) { this._records[sectionType].push(record); } getRecords(sectionTypes, recordType) { let records = []; sectionTypes.forEach((sectionType) => { records = records.concat(this._records[sectionType]); }); if (!recordType) { return records; } return records.filter(r => r.recordType === recordType); } serialize() { let writer = new DataWriter(); // Write leading 0x0000 (2 bytes) writer.putValue(0x0000, 2); // Write `flags` (2 bytes) writer.putValue(_flagsToValue(this._flags), 2); // Write lengths of record sections (2 bytes each) DNS_PACKET_SECTION_TYPES.forEach((sectionType) => { writer.putValue(this._records[sectionType].length, 2); }); // Write records DNS_PACKET_SECTION_TYPES.forEach((sectionType) => { this._records[sectionType].forEach((record) => { writer.putBytes(record.serialize()); }); }); return writer.data; } toJSON() { return JSON.stringify(this.toJSONObject()); } toJSONObject() { let result = {flags: this._flags}; DNS_PACKET_SECTION_TYPES.forEach((sectionType) => { result[sectionType] = []; let records = this._records[sectionType]; records.forEach((record) => { result[sectionType].push(record.toJSONObject()); }); }); return result; } } /** * @private */ function _valueToFlags(value) { return { QR: (value & 0x8000) >> 15, OP: (value & 0x7800) >> 11, AA: (value & 0x0400) >> 10, TC: (value & 0x0200) >> 9, RD: (value & 0x0100) >> 8, RA: (value & 0x0080) >> 7, UN: (value & 0x0040) >> 6, AD: (value & 0x0020) >> 5, CD: (value & 0x0010) >> 4, RC: (value & 0x000f) >> 0 }; } /** * @private */ function _flagsToValue(flags) { let value = 0x0000; value += flags.QR & 0x01; value <<= 4; value += flags.OP & 0x0f; value <<= 1; value += flags.AA & 0x01; value <<= 1; value += flags.TC & 0x01; value <<= 1; value += flags.RD & 0x01; value <<= 1; value += flags.RA & 0x01; value <<= 1; value += flags.UN & 0x01; value <<= 1; value += flags.AD & 0x01; value <<= 1; value += flags.CD & 0x01; value <<= 4; value += flags.RC & 0x0f; return value; }