UNPKG

@esutils/dns-packet

Version:

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

605 lines (594 loc) 19 kB
/* eslint-disable no-param-reassign */ /* eslint-disable no-continue */ /* eslint-disable no-bitwise */ /* eslint-disable max-classes-per-file */ import { invert } from '@esutils/invert'; import { BufferReader } from './reader'; import { BufferWriter } from './writer'; export { BufferReader } from './reader'; export { BufferWriter } from './writer'; /** * [QUERY_TYPE description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.2.2 */ export const 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, }; export const TYPE_INVERTED = invert(TYPE); /** * [QUERY_CLASS description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.2.4 */ export const 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 */ export const EDNS_OPTION_CODE = { ECS: 0x08, }; /** * [Header description] * @param {[type]} options [description] * @docs https://tools.ietf.org/html/rfc1035#section-4.1.1 */ export 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); } } /** * [encode_name description] * @param {[type]} domain [description] * @return {[type]} [description] */ export 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); } } /** * Question section format * @docs https://tools.ietf.org/html/rfc1035#section-4.1.2 */ export class Question { static create(name, type, cls) { return { name: name ?? '', type: type ?? TYPE.ANY, class: cls ?? 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); } } /** * Resource record format * @docs https://tools.ietf.org/html/rfc1035#section-4.1.3 */ export 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); } } /** * [A description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.4.1 */ export 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('.'); } } /** * [AAAA description] * @type {Object} * @docs https://en.wikipedia.org/wiki/IPv6 */ export 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(':'); } } /** * [CNAME description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.3.1 */ export 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); } } /** * [MX description] * @param {[type]} exchange [description] * @param {[type]} priority [description] * @docs https://tools.ietf.org/html/rfc1035#section-3.3.9 */ export 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); } } /** * [NS description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.3.11 */ export 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); } } export 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; */ } } export 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); } } export 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); } } export 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; */ } } export function createDnsBasic() { return { name: '', type: TYPE.ANY, class: CLASS.ANY, }; } 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); } } } export function encodeResponseDefault(writer, info) { switch (info.type) { case TYPE.A: ResourceA.encode(writer, info); break; case TYPE.AAAA: ResourceAAAA.encode(writer, info); break; case TYPE.CNAME: case TYPE.PTR: ResourceCNAME.encode(writer, info); break; default: throw new Error(`Not supported DNS TYPE ${TYPE_INVERTED[info.type]}:${info.type}`); } } export function decodeResponseDefault(reader, info) { const length = Resource.decode(reader, info); switch (info.type) { case TYPE.A: ResourceA.decode(reader, length, info); break; case TYPE.AAAA: ResourceAAAA.decode(reader, length, info); break; case TYPE.CNAME: case 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 ${TYPE_INVERTED[info.type]}:${info.type}`); } } } export 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 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 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(); } } //# sourceMappingURL=dns-packet.js.map