UNPKG

@esutils/dns-packet

Version:

A minimal dns-packet library that implemented in `typescript`

627 lines (616 loc) 20.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Packet = exports.decodeResponseDefault = exports.encodeResponseDefault = exports.createDnsBasic = exports.ResourceEDNS = exports.ResourceSRV = exports.ResourceSOA = exports.ResourceSPF = exports.ResourceNS = exports.ResourceMX = exports.ResourceCNAME = exports.ResourceAAAA = exports.ResourceA = exports.Resource = exports.Question = exports.Name = exports.Header = exports.EDNS_OPTION_CODE = exports.CLASS = exports.TYPE_INVERTED = exports.TYPE = exports.BufferWriter = exports.BufferReader = void 0; /* eslint-disable no-param-reassign */ /* eslint-disable no-continue */ /* eslint-disable no-bitwise */ /* eslint-disable max-classes-per-file */ const invert_1 = require("@esutils/invert"); const reader_1 = require("./reader"); const writer_1 = require("./writer"); var reader_2 = require("./reader"); Object.defineProperty(exports, "BufferReader", { enumerable: true, get: function () { return reader_2.BufferReader; } }); var writer_2 = require("./writer"); Object.defineProperty(exports, "BufferWriter", { enumerable: true, get: function () { return writer_2.BufferWriter; } }); /** * [QUERY_TYPE description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.2.2 */ exports.TYPE = { A: 0x01, NS: 0x02, MD: 0x03, MF: 0x04, CNAME: 0x05, SOA: 0x06, MB: 0x07, MG: 0x08, MR: 0x09, NULL: 0x0a, WKS: 0x0b, PTR: 0x0c, HINFO: 0x0d, MINFO: 0x0e, MX: 0x0f, TXT: 0x10, AAAA: 0x1c, SRV: 0x21, EDNS: 0x29, SPF: 0x63, AXFR: 0xfc, MAILB: 0xfd, MAILA: 0xfe, ANY: 0xff, CAA: 0x101, }; exports.TYPE_INVERTED = (0, invert_1.invert)(exports.TYPE); /** * [QUERY_CLASS description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.2.4 */ exports.CLASS = { IN: 0x01, CS: 0x02, CH: 0x03, HS: 0x04, ANY: 0xff, }; /** * [EDNS_OPTION_CODE description] * @type {Object} * @docs https://tools.ietf.org/html/rfc6891#section-6.1.2 */ exports.EDNS_OPTION_CODE = { ECS: 0x08, }; /** * [Header description] * @param {[type]} options [description] * @docs https://tools.ietf.org/html/rfc1035#section-4.1.1 */ class Header { static create() { return { id: 0, qr: 0, opcode: 0, aa: 0, tc: 0, rd: 0, ra: 0, z: 0, rcode: 0, qdcount: 0, nscount: 0, arcount: 0, ancount: 0, }; } /** * [parse description] * @param {[type]} buffer [description] * @return {[type]} [description] * @docs https://tools.ietf.org/html/rfc1035#section-4.1.1 */ static decode(reader) { const header = Header.create(); header.id = reader.read(16); header.qr = reader.read(1); header.opcode = reader.read(4); header.aa = reader.read(1); header.tc = reader.read(1); header.rd = reader.read(1); header.ra = reader.read(1); header.z = reader.read(3); header.rcode = reader.read(4); header.qdcount = reader.read(16); header.ancount = reader.read(16); header.nscount = reader.read(16); header.arcount = reader.read(16); return header; } /** * [toBuffer description] * @return {[type]} [description] */ static encode(writer, header) { writer.write(header.id, 16); writer.write(header.qr, 1); writer.write(header.opcode, 4); writer.write(header.aa, 1); writer.write(header.tc, 1); writer.write(header.rd, 1); writer.write(header.ra, 1); writer.write(header.z, 3); writer.write(header.rcode, 4); writer.write(header.qdcount, 16); writer.write(header.ancount, 16); writer.write(header.nscount, 16); writer.write(header.arcount, 16); } } exports.Header = Header; /** * [encode_name description] * @param {[type]} domain [description] * @return {[type]} [description] */ class Name { static COPY = 0xc0; static decode(reader) { const name = []; let o; let len = reader.read(8); while (len) { if ((len & Name.COPY) === Name.COPY) { len -= Name.COPY; len <<= 8; const pos = len + reader.read(8); if (!o) o = reader.offset; reader.offset = pos * 8; len = reader.read(8); continue; } else { let part = ''; while (len > 0) { len -= 1; part += String.fromCharCode(reader.read(8)); } name.push(part); len = reader.read(8); } } if (o) reader.offset = o; return name.join('.'); } static encode(domain, writer) { // TODO: domain name compress (domain || '') .split('.') .filter((part) => !!part) .forEach((part) => { writer.write(part.length, 8); part.split('').map((c) => { writer.write(c.charCodeAt(0), 8); return c.charCodeAt(0); }); }); writer.write(0, 8); } } exports.Name = Name; /** * Question section format * @docs https://tools.ietf.org/html/rfc1035#section-4.1.2 */ class Question { static create(name, type, cls) { return { name: name ?? '', type: type ?? exports.TYPE.ANY, class: cls ?? exports.CLASS.ANY, }; } /** * [parse description] * @param {[type]} reader [description] * @return {[type]} [description] */ static decode(reader, question) { question.name = Name.decode(reader); question.type = reader.read(16); question.class = reader.read(16); } static encode(writer, question) { Name.encode(question.name, writer); writer.write(question.type, 16); writer.write(question.class, 16); } } exports.Question = Question; /** * Resource record format * @docs https://tools.ietf.org/html/rfc1035#section-4.1.3 */ class Resource { /** * [encode description] * @param writer [description] * @param info [description] * @return The offset of the reserved 16 bits for resource body length */ static encode(writer, info) { Name.encode(info.name, writer); writer.write(info.type, 16); writer.write(info.class, 16); writer.write(info.ttl, 32); // Reserve 16 bits for resource length const offset = writer.buffer.length; writer.write(0, 16); return offset; } static encodeLength(writer, offset) { const lengthInBits = writer.buffer.length - offset - 16; writer.update(offset, lengthInBits / 8, 16); } /** * [decode description] * @param {[type]} reader [description] * @return The length of the resource body */ static decode(reader, info) { info.name = Name.decode(reader); info.type = reader.read(16); info.class = reader.read(16); info.ttl = reader.read(32); return reader.read(16); } } exports.Resource = Resource; /** * [A description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.4.1 */ class ResourceA { static encode(writer, info) { const offset = Resource.encode(writer, info); const parts = info.address.split('.'); parts.forEach((part) => { writer.write(parseInt(part, 10), 8); }); Resource.encodeLength(writer, offset); } static decode(reader, length, info) { const parts = []; while (length > 0) { length -= 1; parts.push(reader.read(8)); } info.address = parts.join('.'); } } exports.ResourceA = ResourceA; /** * [AAAA description] * @type {Object} * @docs https://en.wikipedia.org/wiki/IPv6 */ class ResourceAAAA { static encode(writer, info) { const offset = Resource.encode(writer, info); const parts = info.address.split(':'); parts.forEach((part) => { writer.write(parseInt(part, 16), 16); }); Resource.encodeLength(writer, offset); } static decode(reader, length, info) { const parts = []; while (length) { length -= 2; parts.push(reader.read(16)); } info.address = parts .map((part) => (part > 0 ? part.toString(16) : '')) .join(':'); } } exports.ResourceAAAA = ResourceAAAA; /** * [CNAME description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.3.1 */ class ResourceCNAME { static encode(writer, info) { const offset = Resource.encode(writer, info); Name.encode(info.domain, writer); Resource.encodeLength(writer, offset); } static decode(reader, _length, info) { info.domain = Name.decode(reader); } } exports.ResourceCNAME = ResourceCNAME; /** * [MX description] * @param {[type]} exchange [description] * @param {[type]} priority [description] * @docs https://tools.ietf.org/html/rfc1035#section-3.3.9 */ class ResourceMX { static encode(writer, info) { const offset = Resource.encode(writer, info); writer.write(info.priority, 16); Name.encode(info.exchange, writer); Resource.encodeLength(writer, offset); } static decode(reader, length, info) { info.priority = reader.read(16); info.exchange = Name.decode(reader); } } exports.ResourceMX = ResourceMX; /** * [NS description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.3.11 */ class ResourceNS { static encode(writer, info) { const offset = Resource.encode(writer, info); Name.encode(info.ns, writer); Resource.encodeLength(writer, offset); } static decode(reader, length, info) { info.ns = Name.decode(reader); } } exports.ResourceNS = ResourceNS; class ResourceSPF { static encode(writer, info) { const offset = Resource.encode(writer, info); /* writer = writer || new Packet.Writer(); // make sure that resource data is a an array of strings const characterStrings = Array.isArray(record.data) ? record.data : [ record.data ]; // convert array of strings to array of buffers const characterStringBuffers = characterStrings.map(function(characterString) { if (Buffer.isBuffer(characterString)) { return characterString; } if (typeof characterString === 'string') { return Buffer.from(characterString, 'utf8'); } return false; }).filter(function(characterString) { // remove invalid values from the array return characterString; }); // calculate byte length of resource strings const bufferLength = characterStringBuffers.reduce(function(sum, characterStringBuffer) { return sum + characterStringBuffer.length; }, 0); // write string length to output writer.write(bufferLength + characterStringBuffers.length, 16); // response length // write each string to output characterStringBuffers.forEach(function(buffer) { writer.write(buffer.length, 8); // text length buffer.forEach(function(c) { writer.write(c, 8); }); }); return writer.toBuffer(); */ Resource.encodeLength(writer, offset); } static decode(_reader, _length, _info) { /* const parts = []; let bytesRead = 0; let chunkLength = 0; while (bytesRead < length) { chunkLength = reader.read(8); // text length bytesRead++; while (chunkLength--) { parts.push(reader.read(8)); bytesRead++; } } this.data = Buffer.from(parts).toString('utf8'); return this; */ } } exports.ResourceSPF = ResourceSPF; class ResourceSOA { static encode(writer, info) { const offset = Resource.encode(writer, info); Name.encode(info.primary, writer); Name.encode(info.admin, writer); writer.write(info.serial, 32); writer.write(info.refresh, 32); writer.write(info.retry, 32); writer.write(info.expiration, 32); writer.write(info.minimum, 32); Resource.encodeLength(writer, offset); } static decode(reader, _length, info) { info.primary = Name.decode(reader); info.admin = Name.decode(reader); info.serial = reader.read(32); info.refresh = reader.read(32); info.retry = reader.read(32); info.expiration = reader.read(32); info.minimum = reader.read(32); } } exports.ResourceSOA = ResourceSOA; class ResourceSRV { static encode(writer, info) { const offset = Resource.encode(writer, info); writer.write(info.priority, 16); writer.write(info.weight, 16); writer.write(info.port, 16); Name.encode(info.target, writer); Resource.encodeLength(writer, offset); } static decode(reader, length, info) { info.priority = reader.read(16); info.weight = reader.read(16); info.port = reader.read(16); info.target = Name.decode(reader); } } exports.ResourceSRV = ResourceSRV; class ResourceEDNS { static encode(writer, info) { const offset = Resource.encode(writer, info); /* const rdataWriter = new Packet.Writer(); for (const rdata of record.rdata) { const encoder = Object.keys(Packet.EDNS_OPTION_CODE).filter(function(type) { return rdata.ednsCode === Packet.EDNS_OPTION_CODE[type]; })[0]; if (encoder in Packet.Resource.EDNS && Packet.Resource.EDNS[encoder].encode) { const w = new Packet.Writer(); Packet.Resource.EDNS[encoder].encode(rdata, w); rdataWriter.write(rdata.ednsCode, 16); rdataWriter.write(w.buffer.length / 8, 16); rdataWriter.writeBuffer(w); } else { debug('node-dns > unknown EDNS rdata encoder %s(%j)', encoder, rdata.ednsCode); } } writer = writer || new Packet.Writer(); writer.write(rdataWriter.buffer.length / 8, 16); writer.writeBuffer(rdataWriter); return writer.toBuffer(); */ Resource.encodeLength(writer, offset); } static decode(_reader, _length, _info) { /* this.type = Packet.TYPE.EDNS; this.class = 512; this.ttl = 0; this.rdata = []; while (length) { const optionCode = reader.read(16); const optionLength = reader.read(16); // In octet (https://tools.ietf.org/html/rfc6891#page-8) const decoder = Object.keys(Packet.EDNS_OPTION_CODE).filter(function(type) { return optionCode === Packet.EDNS_OPTION_CODE[type]; })[0]; if (decoder in Packet.Resource.EDNS && Packet.Resource.EDNS[decoder].decode) { const rdata = Packet.Resource.EDNS[decoder].decode(reader, optionLength); this.rdata.push(rdata); } else { reader.read(optionLength); // Ignore data that doesn't understand debug('node-dns > unknown EDNS rdata decoder %s(%j)', decoder, optionCode); } length = length - 4 - optionLength; } return this; */ } } exports.ResourceEDNS = ResourceEDNS; function createDnsBasic() { return { name: '', type: exports.TYPE.ANY, class: exports.CLASS.ANY, }; } exports.createDnsBasic = createDnsBasic; function decodeSingle(reader, parsed, errors, decode, count) { for (let i = 0; i < count; i += 1) { try { const info = createDnsBasic(); decode(reader, info); parsed.push(info); } catch (e) { errors.push(e); } } } function encodeSingle(writer, dnsBasicList, errors, encode) { for (let i = 0; i < dnsBasicList.length; i += 1) { try { encode(writer, dnsBasicList[i]); } catch (e) { errors.push(e); } } } function encodeResponseDefault(writer, info) { switch (info.type) { case exports.TYPE.A: ResourceA.encode(writer, info); break; case exports.TYPE.AAAA: ResourceAAAA.encode(writer, info); break; case exports.TYPE.CNAME: case exports.TYPE.PTR: ResourceCNAME.encode(writer, info); break; default: throw new Error(`Not supported DNS TYPE ${exports.TYPE_INVERTED[info.type]}:${info.type}`); } } exports.encodeResponseDefault = encodeResponseDefault; function decodeResponseDefault(reader, info) { const length = Resource.decode(reader, info); switch (info.type) { case exports.TYPE.A: ResourceA.decode(reader, length, info); break; case exports.TYPE.AAAA: ResourceAAAA.decode(reader, length, info); break; case exports.TYPE.CNAME: case exports.TYPE.PTR: ResourceCNAME.decode(reader, length, info); break; default: { for (let lengthToRead = length; lengthToRead > 0; lengthToRead -= 1) { reader.read(8); } throw new Error(`Not supported DNS TYPE ${exports.TYPE_INVERTED[info.type]}:${info.type}`); } } } exports.decodeResponseDefault = decodeResponseDefault; class Packet { static create() { return { header: Header.create(), errors: [], questions: [], answers: [], authorities: [], additionals: [], }; } /** * [random header id] * @return {[type]} [description] */ static randomHeaderId() { return (Math.random() * 65535) | 0; } static decode(buffer, decoder) { const reader = new reader_1.BufferReader(buffer); const dnsParsed = Packet.create(); const header = Header.decode(reader); dnsParsed.header = header; decodeSingle(reader, dnsParsed.questions, dnsParsed.errors, Question.decode, header.qdcount); decodeSingle(reader, dnsParsed.answers, dnsParsed.errors, decoder, header.ancount); decodeSingle(reader, dnsParsed.authorities, dnsParsed.errors, decoder, header.nscount); decodeSingle(reader, dnsParsed.additionals, dnsParsed.errors, decoder, header.arcount); return dnsParsed; } static encode(dnsEntry, encoder) { const writer = new writer_1.BufferWriter(); dnsEntry.header.qdcount = dnsEntry.questions.length; dnsEntry.header.ancount = dnsEntry.answers.length; dnsEntry.header.nscount = dnsEntry.authorities.length; dnsEntry.header.arcount = dnsEntry.additionals.length; Header.encode(writer, dnsEntry.header); encodeSingle(writer, dnsEntry.questions, dnsEntry.errors, Question.encode); encodeSingle(writer, dnsEntry.answers, dnsEntry.errors, encoder); encodeSingle(writer, dnsEntry.authorities, dnsEntry.errors, encoder); encodeSingle(writer, dnsEntry.additionals, dnsEntry.errors, encoder); return writer.toBuffer(); } } exports.Packet = Packet; //# sourceMappingURL=dns-packet.js.map