UNPKG

dns2

Version:

A DNS Server and Client Implementation in Pure JavaScript with no dependencies.

1,566 lines (1,506 loc) 54.4 kB
const net = require('node:net'); const { debuglog, inspect } = require('node:util'); const { randomInt } = require('node:crypto'); const BufferReader = require('./lib/reader'); const BufferWriter = require('./lib/writer'); const debug = debuglog('dns2'); // Canonical IPv6 text form per RFC 5952: // - lower case hex, no leading zeros per group (handled by toString(16)) // - the longest run of >= 2 zero groups is replaced with "::" // - on ties, the first such run is chosen // - a single zero group is NOT compressed const toIPv6 = buffer => { const segments = buffer.map(part => (part > 0 ? part.toString(16) : '0')); let bestStart = -1; let bestLen = 0; let curStart = -1; let curLen = 0; for (let i = 0; i < segments.length; i++) { if (segments[i] === '0') { if (curLen === 0) curStart = i; curLen++; if (curLen > bestLen) { bestLen = curLen; bestStart = curStart; } } else { curLen = 0; } } if (bestLen < 2) return segments.join(':'); const before = segments.slice(0, bestStart).join(':'); const after = segments.slice(bestStart + bestLen).join(':'); return `${before}::${after}`; }; const fromIPv6 = address => { const digits = address.split(':'); // Leading/trailing "::" produces an empty leading/trailing element that is // not a zero group of its own; drop it so only the interior "" marks the run. if (digits[0] === '') { digits.shift(); } if (digits[digits.length - 1] === '') { digits.pop(); } // The interior empty string occupies a slot of its own, so it stands in for // one more group than the shortfall in `digits`. const missingFields = 8 - digits.length + 1; return digits.flatMap(digit => digit === '' ? Array(missingFields).fill('0') : digit.padStart(4, '0'), ); }; /** * [Packet description] * @param {[type]} data [description] * @docs https://tools.ietf.org/html/rfc1034 * @docs https://tools.ietf.org/html/rfc1035 * * <Buffer 29 64 01 00 00 01 00 00 00 00 00 00 * |-ID----------- HEADER ----------->| * * 03 77 77 77 01 7a 02 63 6e 00 00 01 00 01> * <-W--W--W-----Z-----C--N>|<----------->| */ function Packet(data) { this.header = {}; this.questions = []; this.answers = []; this.authorities = []; this.additionals = []; // Populated by Packet.parse with one Packet.DecodeError per record it could // not decode; empty for messages built in memory or parsed cleanly. this.errors = []; if (data instanceof Packet) { return data; } else if (data instanceof Packet.Header) { this.header = data; } else if (data instanceof Packet.Question) { this.questions.push(data); } else if (data instanceof Packet.Resource) { this.answers.push(data); } else if (typeof data === 'string') { this.questions.push(data); } else if (typeof data === 'object') { const type = {}.toString.call(data).match(/\[object (\w+)\]/)[1]; if (type === 'Array') { this.questions = data; } if (type === 'Object') { this.header = data; } } return this; } // Octets in a DNS message header (RFC 1035 §4.1.1). Packet.HEADER_SIZE = 12; /** * [QUERY_TYPE description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.2.2 */ Packet.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, RRSIG: 0x2e, SPF: 0x63, AXFR: 0xfc, MAILB: 0xfd, MAILA: 0xfe, ANY: 0xff, CAA: 0x101, DNSKEY: 0x30, }; /** * Reverse of Packet.TYPE, used to dispatch rdata codecs and to name types in * diagnostics. * @type {Object} */ Packet.TYPE_NAME = Object.fromEntries( Object.entries(Packet.TYPE).map(([name, code]) => [code, name]), ); /** * Name of a type code, falling back to the RFC 3597 §5 "TYPE<n>" presentation * for types this library has no codec for. * @param {number} code * @return {string} */ Packet.typeName = code => Packet.TYPE_NAME[code] || `TYPE${code}`; /** * [QUERY_CLASS description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.2.4 */ Packet.CLASS = { IN: 0x01, CS: 0x02, CH: 0x03, HS: 0x04, ANY: 0xff, }; /** * DNS response codes * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-4.1.1 */ Packet.RCODE = { NOERROR: 0, FORMERR: 1, SERVFAIL: 2, NXDOMAIN: 3, NOTIMP: 4, REFUSED: 5, YXDOMAIN: 6, YXRRSET: 7, NXRRSET: 8, NOTAUTH: 9, NOTZONE: 10, DSOTYPENI: 11, // Codes above 15 do not fit the header's 4-bit RCODE field: the high byte // travels in an OPT record's TTL (RFC 6891 §6.1.3), so a response using one // MUST carry an OPT. Packet.toBuffer performs that split. // // 16 has two assignments in the IANA registry — BADVERS for an unsupported // EDNS version (RFC 6891) and BADSIG for a TSIG failure (RFC 8945). They // share the code point on the wire; only context tells them apart. BADVERS: 16, BADSIG: 16, BADKEY: 17, BADTIME: 18, BADMODE: 19, BADNAME: 20, BADALG: 21, BADTRUNC: 22, BADCOOKIE: 23, }; /** * [EDNS_OPTION_CODE description] * @type {Object} * @docs https://tools.ietf.org/html/rfc6891#section-6.1.2 */ Packet.EDNS_OPTION_CODE = { ECS: 0x08, EDE: 0x0f, }; /** * Extended DNS Error INFO-CODEs. These explain a response; they do not replace * its RCODE. Codes past 24 are later registry additions, some originating from * drafts or vendor implementations rather than a published RFC. * @type {Object} * @docs https://tools.ietf.org/html/rfc8914#section-4 * @docs https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#extended-dns-error-codes */ Packet.EDE = { OTHER: 0, UNSUPPORTED_DNSKEY_ALGORITHM: 1, UNSUPPORTED_DS_DIGEST_TYPE: 2, STALE_ANSWER: 3, FORGED_ANSWER: 4, DNSSEC_INDETERMINATE: 5, DNSSEC_BOGUS: 6, SIGNATURE_EXPIRED: 7, SIGNATURE_NOT_YET_VALID: 8, DNSKEY_MISSING: 9, RRSIGS_MISSING: 10, NO_ZONE_KEY_BIT_SET: 11, NSEC_MISSING: 12, CACHED_ERROR: 13, NOT_READY: 14, BLOCKED: 15, CENSORED: 16, FILTERED: 17, PROHIBITED: 18, STALE_NXDOMAIN_ANSWER: 19, NOT_AUTHORITATIVE: 20, NOT_SUPPORTED: 21, NO_REACHABLE_AUTHORITY: 22, NETWORK_ERROR: 23, INVALID_DATA: 24, SIGNATURE_EXPIRED_BEFORE_VALID: 25, TOO_EARLY: 26, UNSUPPORTED_NSEC3_ITERATIONS: 27, UNABLE_TO_CONFORM_TO_POLICY: 28, SYNTHESIZED: 29, INVALID_QUERY_TYPE: 30, RATE_LIMITED: 31, OVER_QUOTA: 32, NEGATIVE_TRUST_ANCHOR: 33, NEW_DELEGATION_ONLY: 34, }; /** * Reverse of Packet.EDE, for naming a received INFO-CODE in diagnostics. * @type {Object} */ Packet.EDE_NAME = Object.fromEntries( Object.entries(Packet.EDE).map(([name, code]) => [code, name]), ); /** * Reverse of Packet.EDNS_OPTION_CODE. * @type {Object} */ Packet.EDNS_OPTION_NAME = Object.fromEntries( Object.entries(Packet.EDNS_OPTION_CODE).map(([name, code]) => [code, name]), ); /** * Generate a cryptographically random 16-bit DNS transaction ID. * RFC 5452 §3 — the full 16-bit space must be used from a CSPRNG to make * response forgery / cache poisoning impractical. * @return {number} integer in [0, 0xFFFF] */ Packet.uuid = function () { return randomInt(0x10000); }; /** * A record, question, or message that could not be decoded. * * Records that fail to decode are dropped rather than half-populated, so the * reason has to travel separately: Packet.parse collects one of these per * failure on `packet.errors`, and throws one when the message itself is * unusable. * * @property {string} [section] questions / answers / authorities / additionals * @property {number} [index] position of the record within that section * @property {number} [offset] octet offset in the message where it started * @property {boolean} recovered whether decoding resumed after this failure */ class DecodeError extends Error { constructor(message, context = {}) { const { section, index, offset, cause } = context; const where = section === undefined ? '' : `${section}[${index}]${offset === undefined ? '' : ` at offset ${offset}`}: `; super(`${where}${message}`, cause ? { cause } : undefined); this.name = 'DecodeError'; Object.assign(this, context); this.recovered = !!context.recovered; } } Packet.DecodeError = DecodeError; /** * [parse description] * @param {[type]} buffer [description] * @return {[type]} [description] * @throws {Packet.DecodeError} when the message has no usable header; per-record * failures are reported on the returned packet's `errors` array */ Packet.parse = function (buffer) { if (!Buffer.isBuffer(buffer)) { throw new DecodeError( `expected a Buffer, got ${buffer === null ? 'null' : typeof buffer}`, ); } if (buffer.length < Packet.HEADER_SIZE) { throw new DecodeError( `message is ${buffer.length} octets, too short for the ` + `${Packet.HEADER_SIZE}-octet header (RFC 1035 §4.1.1)`, ); } const packet = new Packet(); const reader = new Packet.Reader(buffer); packet.header = Packet.Header.parse(reader); // A failure that left the reader misaligned makes every later record in the // message garbage, so parsing stops there rather than manufacturing junk // records. Failures confined to one record's RDATA are recoverable: the // reader is repositioned by RDLENGTH and the next record still decodes. sections: for (const [section, decoder, count] of [ ['questions', Packet.Question, packet.header.qdcount], ['answers', Packet.Resource, packet.header.ancount], ['authorities', Packet.Resource, packet.header.nscount], ['additionals', Packet.Resource, packet.header.arcount], ]) { for (let index = 0; index < count; index++) { const offset = reader.offset / 8; try { packet[section].push(decoder.parse(reader)); } catch (cause) { const error = new DecodeError(cause.message, { section, index, offset, recovered: !!cause.recovered, cause, }); packet.errors.push(error); debug('node-dns > %s', error.message); if (!error.recovered) break sections; } } } // RFC 6891 §6.1.3: when an OPT record is present the wire RCODE is 12 bits: // the 4 low bits come from the header, the 8 high bits come from the OPT // record's TTL high byte. Merge them so callers see the full 12-bit value. const opt = packet.additionals.find(r => r && r.type === Packet.TYPE.EDNS); if (opt && opt.extendedRcode) { packet.header.rcode = (opt.extendedRcode << 4) | (packet.header.rcode & 0xf); } return packet; }; /** * recursive */ Object.defineProperty(Packet.prototype, 'recursive', { enumerable: true, configurable: true, get() { return !!this.header.rd; }, set(yn) { this.header.rd = +yn; }, }); /** * [toBuffer description] * @return {[type]} [description] */ Packet.prototype.toBuffer = function (writer) { writer = writer || new Packet.Writer(); // RFC 1035 §4.1.4 — record the byte offset of each name we encode so later // occurrences can be replaced by a compression pointer. The map is owned by // the top-level message writer; rdata encoders that recursively encode // names participate automatically. if (!writer.names) writer.names = new Map(); this.header.qdcount = this.questions.length; this.header.ancount = this.answers.length; this.header.nscount = this.authorities.length; this.header.arcount = this.additionals.length; if (!(this instanceof Packet.Header)) { this.header = new Packet.Header(this.header); } // RFC 6891 §6.1.3: if the caller set a header.rcode >= 16 the high byte must // be carried in the OPT record's TTL. Propagate it before the header is // serialized so the low nibble alone goes into the header. if (this.header.rcode > 0xf) { const opt = this.additionals.find(r => r && r.type === Packet.TYPE.EDNS); if (opt) { opt.extendedRcode = (this.header.rcode >>> 4) & 0xff; opt.ttl = ednsTtl(opt.extendedRcode, opt.version || 0, opt.doFlag); } else { debug( 'node-dns > rcode %d > 15 but no OPT record; truncating to low nibble', this.header.rcode, ); } } this.header.toBuffer(writer); [ // section encoder ['questions', Packet.Question], ['answers', Packet.Resource], ['authorities', Packet.Resource], ['additionals', Packet.Resource], ].forEach( function (def) { const section = def[0]; const Encoder = def[1]; (this[section] || []).forEach(function (resource) { Encoder.encode(resource, writer); }); }.bind(this), ); return writer.toBuffer(); }; /** * [Header description] * @param {[type]} options [description] * @docs https://tools.ietf.org/html/rfc1035#section-4.1.1 */ Packet.Header = function (header) { this.id = 0; this.qr = 0; this.opcode = 0; this.aa = 0; this.tc = 0; this.rd = 0; this.ra = 0; this.z = 0; this.ad = 0; this.cd = 0; this.rcode = 0; this.qdcount = 0; this.ancount = 0; this.nscount = 0; this.arcount = 0; for (const k in header) { this[k] = header[k]; } return this; }; /** * [parse description] * @param {[type]} buffer [description] * @return {[type]} [description] * @docs https://tools.ietf.org/html/rfc1035#section-4.1.1 */ Packet.Header.parse = function (reader) { const header = new Packet.Header(); if (reader instanceof Buffer) { reader = new Packet.Reader(reader); } 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); // RFC 4035 §3.2.3 repurposed the second and third Z bits as AD and CD. header.z = reader.read(1); header.ad = reader.read(1); header.cd = reader.read(1); 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] */ Packet.Header.prototype.toBuffer = function (writer) { writer = writer || new Packet.Writer(); writer.write(this.id, 16); writer.write(this.qr, 1); writer.write(this.opcode, 4); writer.write(this.aa, 1); writer.write(this.tc, 1); writer.write(this.rd, 1); writer.write(this.ra, 1); // RFC 1035 §4.1.1: the Z bit is reserved and must be zero in outgoing // messages, regardless of what was preserved from any inbound packet. writer.write(0, 1); writer.write(this.ad, 1); writer.write(this.cd, 1); writer.write(this.rcode & 0xf, 4); writer.write(this.qdcount, 16); writer.write(this.ancount, 16); writer.write(this.nscount, 16); writer.write(this.arcount, 16); return writer.toBuffer(); }; /** * Question section format * @docs https://tools.ietf.org/html/rfc1035#section-4.1.2 */ Packet.Question = function (name, type, cls) { const defaults = { type: Packet.TYPE.ANY, class: Packet.CLASS.ANY, }; if (typeof name === 'object') { for (const k in name) { this[k] = name[k] || defaults[k]; } } else { this.name = name; this.type = type || defaults.type; this.class = cls || defaults.class; } return this; }; /** * [toBuffer description] * @param {[type]} writer [description] * @return {[type]} [description] */ Packet.Question.prototype.toBuffer = function (writer) { return Packet.Question.encode(this, writer); }; /** * [parse description] * @param {[type]} reader [description] * @return {[type]} [description] */ Packet.Question.parse = Packet.Question.decode = function (reader) { const question = new Packet.Question(); if (reader instanceof Buffer) { reader = new Packet.Reader(reader); } question.name = Packet.Name.decode(reader); question.type = reader.read(16); question.class = reader.read(16); return question; }; // A non-numeric TYPE or CLASS would be written as 16 zero bits, turning a typo // such as Packet.TYPE.AAA (undefined) into a valid-looking type 0 on the wire. const assertCode = (value, field, context) => { if (!Number.isInteger(value) || value < 0 || value > 0xffff) { // inspect, not JSON.stringify: the latter renders NaN and Infinity as // "null". Nor String(), which renders the string '1' as 1 — the very // confusion this message exists to resolve. throw new Error( `${context}: ${field} must be a 16-bit integer, got ${inspect(value)}`, ); } }; Packet.Question.encode = function (question, writer) { const ownsWriter = !writer; writer = writer || new Packet.Writer(); assertCode(question.type, 'type', `Question encode "${question.name}"`); assertCode(question.class, 'class', `Question encode "${question.name}"`); Packet.Name.encode(question.name, writer); writer.write(question.type, 16); writer.write(question.class, 16); return ownsWriter ? writer.toBuffer() : undefined; }; /** * Resource record format * @docs https://tools.ietf.org/html/rfc1035#section-4.1.3 */ Packet.Resource = function (name, type, cls, ttl) { const defaults = { name: '', ttl: 300, type: Packet.TYPE.ANY, class: Packet.CLASS.ANY, }; let input; if (typeof name === 'object') { input = name; } else { input = { name, type, class: cls, ttl, }; } Object.assign(this, defaults, input); return this; }; /** * [toBuffer description] * @param {[type]} writer [description] * @return {[type]} [description] */ Packet.Resource.prototype.toBuffer = function (writer) { return Packet.Resource.encode(this, writer); }; /** * [encode description] * @param {[type]} resource [description] * @param {[type]} writer [description] * @return {[type]} [description] */ Packet.Resource.encode = function (resource, writer) { writer = writer || new Packet.Writer(); assertCode(resource.type, 'type', `Resource encode "${resource.name}"`); assertCode(resource.class, 'class', `Resource encode "${resource.name}"`); Packet.Name.encode(resource.name, writer); writer.write(resource.type, 16); writer.write(resource.class, 16); // RFC 2181 §8: TTL is an unsigned 32-bit value but high-bit values are // historically unsafe; clamp to 2^31 - 1 on the wire. writer.write(Math.min(resource.ttl >>> 0, 0x7fffffff), 32); const encoder = Packet.TYPE_NAME[resource.type]; // RDLENGTH is owned here, not by each rdata encoder. We write a 16-bit // placeholder, dispatch to the rdata encoder, then back-fill the length. // This is what lets rdata encoders use compression pointers without having // to predict their compressed length up front. const rdlenBitPos = writer.bitLength(); writer.write(0, 16); const rdataBitStart = writer.bitLength(); const codec = encoder && Packet.Resource[encoder]; if (codec && codec.encode) { codec.encode(resource, writer); } else { debug('node-dns > unknown encoder %s(%j)', encoder, resource.type); // Fallback for unknown / decoder-only types: round-trip the raw RDATA the // decoder preserved as `resource.data`. Without this, RDATA would be // omitted entirely, truncating the wire format and corrupting any // records that follow. const data = Buffer.isBuffer(resource.data) ? resource.data : Buffer.alloc(0); for (const byte of data) { writer.write(byte, 8); } } const rdlen = (writer.bitLength() - rdataBitStart) / 8; writer.patch(rdlenBitPos, rdlen, 16); return writer.toBuffer(); }; /** * [parse description] * @param {[type]} reader [description] * @return {[type]} [description] */ Packet.Resource.parse = Packet.Resource.decode = function (reader) { if (reader instanceof Buffer) { reader = new Packet.Reader(reader); } let resource = new Packet.Resource(); resource.name = Packet.Name.decode(reader); resource.type = reader.read(16); resource.class = reader.read(16); resource.ttl = reader.read(32); // RFC 2181 §8: TTLs are an unsigned 32-bit field but legacy implementations // treated them as signed. Anything with the high bit set is clamped to // 2^31 - 1 so it cannot be misinterpreted as a negative value. if (resource.ttl > 0x7fffffff) resource.ttl = 0x7fffffff; const length = reader.read(16); const label = `${Packet.typeName(resource.type)} record "${resource.name}"`; if (length * 8 > reader.remaining()) { throw new Error( `${label} declares RDLENGTH ${length} but only ` + `${reader.remaining() / 8} octet(s) remain in the message`, ); } // RDLENGTH delimits the record on the wire, so it — not the rdata decoder — // decides where the next record begins. Restoring the cursor to that boundary // keeps a malformed record from cascading into the ones that follow, and lets // Packet.parse report the failure as recoverable. const rdataStart = reader.offset; const rdataEnd = rdataStart + length * 8; const parser = Packet.TYPE_NAME[resource.type]; const codec = parser && Packet.Resource[parser]; try { if (codec && codec.decode) { resource = codec.decode.call(resource, reader, length); if (reader.offset !== rdataEnd) { throw new Error( `${label} rdata consumed ${(reader.offset - rdataStart) / 8} ` + `octet(s), RDLENGTH declares ${length}`, ); } } else { debug('node-dns > unknown parser type: %s(%j)', parser, resource.type); // RFC 3597 §5: retain unknown rdata verbatim so it can be re-emitted. resource.data = Buffer.from( reader.buffer.subarray(rdataStart / 8, rdataEnd / 8), ); } } catch (cause) { cause.recovered = true; throw cause; } finally { reader.offset = rdataEnd; } return resource; }; /** * [encode_name description] * @param {[type]} domain [description] * @return {[type]} [description] */ // RFC 1035 §2.3.4 — wire-format limits. Packet.Name = { COPY: 0xc0, MAX_LABEL: 63, MAX_NAME: 255, decode: function (reader) { if (reader instanceof Buffer) { reader = new Packet.Reader(reader); } const name = []; let o; let len = reader.read(8); // Track each pointer target we follow. A crafted packet can chain // pointers in a cycle; without this guard, decode would loop forever. const visited = new Set(); // Cumulative wire-format octets consumed for this name. RFC 1035 §2.3.4 // caps the total — including the trailing zero-length root label — at // 255, so the running tally starts at 1 (the terminator) and adds the // length byte + label bytes for each non-root label. let totalOctets = 1; while (len) { if ((len & Packet.Name.COPY) === Packet.Name.COPY) { len -= Packet.Name.COPY; len = len << 8; const pos = len + reader.read(8); if (visited.has(pos)) { throw new Error('Name decode: pointer cycle detected'); } visited.add(pos); if (!o) o = reader.offset; reader.offset = pos * 8; len = reader.read(8); continue; } // RFC 1035: a label length byte has its top two bits clear (00). // The 01/10 combinations are reserved and indicate a malformed name. if (len & 0xc0) { throw new Error( `Name decode: invalid label length byte 0x${len.toString(16)}`, ); } if (len > Packet.Name.MAX_LABEL) { throw new Error( `Name decode: label exceeds ${Packet.Name.MAX_LABEL} octets`, ); } totalOctets += len + 1; if (totalOctets > Packet.Name.MAX_NAME) { throw new Error( `Name decode: name exceeds ${Packet.Name.MAX_NAME} octets`, ); } let part = ''; while (len--) part += String.fromCharCode(reader.read(8)); name.push(part); len = reader.read(8); } if (o) reader.offset = o; return name.join('.'); }, encode: function (domain, writer) { // Only materialize a Buffer when we created the writer; if the caller // passed one, they own the final toBuffer() and we avoid an O(buffer) // materialization per name (a big deal once many records share a suffix). const ownsWriter = !writer; writer = writer || new Packet.Writer(); const parts = (domain || '').split('.').filter(part => !!part); let totalOctets = 1; // root terminator for (const part of parts) { if (part.length > Packet.Name.MAX_LABEL) { throw new Error( `Name encode: label "${part}" is ${part.length} octets ` + `(max ${Packet.Name.MAX_LABEL})`, ); } totalOctets += part.length + 1; } if (totalOctets > Packet.Name.MAX_NAME) { throw new Error( `Name encode: name "${domain}" encodes to ${totalOctets} octets ` + `(max ${Packet.Name.MAX_NAME})`, ); } // RFC 1035 §4.1.4 — if the writer carries a name-offset table, emit a // compression pointer for any suffix we've already serialized; otherwise // record this suffix at its current byte offset so later names can point // here. Compression pointers can address only the first 16 KiB of a // message (14-bit offset); past that we fall back to literal labels. const compress = writer.names instanceof Map; for (let i = 0; i < parts.length; i++) { const suffix = parts.slice(i).join('.').toLowerCase(); if (compress && writer.names.has(suffix)) { writer.write(0xc000 | writer.names.get(suffix), 16); return ownsWriter ? writer.toBuffer() : undefined; } if (compress) { const byteOffset = writer.byteLength(); if (byteOffset < 0x4000) writer.names.set(suffix, byteOffset); } writer.write(parts[i].length, 8); for (let j = 0; j < parts[i].length; j++) { writer.write(parts[i].charCodeAt(j), 8); } } writer.write(0, 8); return ownsWriter ? writer.toBuffer() : undefined; }, }; /** * [A description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.4.1 */ Packet.Resource.A = function (address) { this.type = Packet.TYPE.A; this.class = Packet.CLASS.IN; this.address = address; return this; }; Packet.Resource.A.encode = function (record, writer) { writer = writer || new Packet.Writer(); // Without this check a malformed address writes NaN octets, silently // encoding as 0.0.0.0 on the wire. if (!net.isIPv4(record.address)) { throw new Error( `A encode: invalid IPv4 address ${JSON.stringify(record.address)}`, ); } // RDLENGTH is written by Packet.Resource.encode; only emit the rdata here. // No toBuffer() — the caller owns materialization (avoids O(N) re-walks of // the message bit-array per record). record.address.split('.').forEach(function (part) { writer.write(parseInt(part, 10), 8); }); }; Packet.Resource.A.decode = function (reader, length) { // RFC 1035 §3.4.1 — ADDRESS is exactly one 32-bit value. if (length !== 4) { throw new Error(`A decode: RDLENGTH is ${length}, expected 4`); } const parts = []; while (length--) parts.push(reader.read(8)); this.address = parts.join('.'); return this; }; /** * [MX description] * @param {[type]} exchange [description] * @param {[type]} priority [description] * @docs https://tools.ietf.org/html/rfc1035#section-3.3.9 */ Packet.Resource.MX = function (exchange, priority) { this.type = Packet.TYPE.MX; this.class = Packet.CLASS.IN; this.exchange = exchange; this.priority = priority; return this; }; /** * [encode description] * @param {[type]} record [description] * @param {[type]} writer [description] * @return {[type]} [description] */ Packet.Resource.MX.encode = function (record, writer) { writer = writer || new Packet.Writer(); writer.write(record.priority, 16); Packet.Name.encode(record.exchange, writer); }; /** * [decode description] * @param {[type]} reader [description] * @param {[type]} length [description] * @return {[type]} [description] */ Packet.Resource.MX.decode = function (reader, length) { this.priority = reader.read(16); this.exchange = Packet.Name.decode(reader); return this; }; /** * [AAAA description] * @type {Object} * @docs https://en.wikipedia.org/wiki/IPv6 */ Packet.Resource.AAAA = { decode: function (reader, length) { // RFC 3596 §2.2 — a 128-bit address. An odd or short length would step the // `length -= 2` countdown past zero and read into the following records. if (length !== 16) { throw new Error(`AAAA decode: RDLENGTH is ${length}, expected 16`); } const parts = []; while (length) { length -= 2; parts.push(reader.read(16)); } this.address = toIPv6(parts); return this; }, encode: function (record, writer) { writer = writer || new Packet.Writer(); if (!net.isIPv6(record.address)) { throw new Error( `AAAA encode: invalid IPv6 address ${JSON.stringify(record.address)}`, ); } fromIPv6(record.address).forEach(function (part) { writer.write(parseInt(part, 16), 16); }); }, }; /** * [NS description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.3.11 */ Packet.Resource.NS = { decode: function (reader, length) { this.ns = Packet.Name.decode(reader); return this; }, encode: function (record, writer) { writer = writer || new Packet.Writer(); Packet.Name.encode(record.ns, writer); }, }; /** * [CNAME description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.3.1 */ Packet.Resource.PTR = Packet.Resource.CNAME = { decode: function (reader, length) { this.domain = Packet.Name.decode(reader); return this; }, encode: function (record, writer) { writer = writer || new Packet.Writer(); Packet.Name.encode(record.domain, writer); }, }; /** * [SPF description] * @type {[type]} * @docs https://tools.ietf.org/html/rfc1035#section-3.3.14 */ Packet.Resource.SPF = Packet.Resource.TXT = { // RFC 1035 §3.3.14: TXT RDATA is one or more length-prefixed // <character-string> items. Preserve those boundaries by returning an // array — joining them silently corrupts SPF/DKIM and other multi-string // records whose semantics depend on segmentation. decode: function (reader, length) { const strings = []; let bytesRead = 0; while (bytesRead < length) { const chunkLength = reader.read(8); bytesRead++; // A character-string whose length runs past the end of RDATA would make // us read into the next record; Packet.Resource.parse restores the cursor // to the RDLENGTH boundary so the following records still decode. if (chunkLength > length - bytesRead) { throw new Error( `TXT decode: character-string of ${chunkLength} octets overruns ` + `RDATA (${length - bytesRead} octets remaining)`, ); } const bytes = Buffer.alloc(chunkLength); for (let i = 0; i < chunkLength; i++) { bytes[i] = reader.read(8); } bytesRead += chunkLength; strings.push(bytes.toString('utf8')); } this.data = strings; return this; }, encode: function (record, writer) { 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; }); // write each string to output (RDLENGTH is back-filled by Resource.encode) characterStringBuffers.forEach(function (buffer) { writer.write(buffer.length, 8); // text length buffer.forEach(function (c) { writer.write(c, 8); }); }); }, }; /** * [SOA description] * @type {Object} * @docs https://tools.ietf.org/html/rfc1035#section-3.3.13 */ Packet.Resource.SOA = { decode: function (reader, length) { this.primary = Packet.Name.decode(reader); this.admin = Packet.Name.decode(reader); this.serial = reader.read(32); this.refresh = reader.read(32); this.retry = reader.read(32); this.expiration = reader.read(32); this.minimum = reader.read(32); return this; }, encode: function (record, writer) { writer = writer || new Packet.Writer(); Packet.Name.encode(record.primary, writer); Packet.Name.encode(record.admin, writer); writer.write(record.serial, 32); writer.write(record.refresh, 32); writer.write(record.retry, 32); writer.write(record.expiration, 32); // RFC 2308 §4: the SOA minimum field is also a TTL; same 31-bit ceiling. writer.write(Math.min(record.minimum >>> 0, 0x7fffffff), 32); }, }; /** * [SRV description] * @type {Object} * @docs https://tools.ietf.org/html/rfc2782 */ Packet.Resource.SRV = { decode: function (reader, length) { this.priority = reader.read(16); this.weight = reader.read(16); this.port = reader.read(16); this.target = Packet.Name.decode(reader); return this; }, encode: function (record, writer) { writer = writer || new Packet.Writer(); writer.write(record.priority, 16); writer.write(record.weight, 16); writer.write(record.port, 16); Packet.Name.encode(record.target, writer); }, }; // RFC 6891 §6.1.3 — the OPT record's TTL field carries: // bits 0- 7: extended RCODE (high byte of a 12-bit RCODE) // bits 8-15: EDNS version // bit 16: DO (DNSSEC OK) // bits 17-31: reserved Z, must be zero const ednsTtl = (extendedRcode, version, doFlag) => (((extendedRcode & 0xff) << 24) >>> 0) | ((version & 0xff) << 16) | (doFlag ? 0x8000 : 0); // RFC 6891 §6.2.5 — a reasonable default for the requestor's UDP payload size. // The pre-EDNS 512-byte limit is conservative; modern resolvers advertise // 4096 so upstreams need not truncate responses that fit in a typical MTU. Packet.EDNS_DEFAULT_UDP_PAYLOAD_SIZE = 4096; Packet.Resource.EDNS = function (rdata, opts = {}) { const extendedRcode = opts.extendedRcode || 0; const version = opts.version || 0; const doFlag = !!opts.doFlag; const udpPayloadSize = opts.udpPayloadSize || Packet.EDNS_DEFAULT_UDP_PAYLOAD_SIZE; return { type: Packet.TYPE.EDNS, class: udpPayloadSize, ttl: ednsTtl(extendedRcode, version, doFlag), extendedRcode, version, doFlag, rdata, // Objects of type Packet.Resource.EDNS.* }; }; Packet.Resource.EDNS.decode = function (reader, length) { // When invoked through Resource.parse, this.type/class/ttl are already set // from the wire. Direct callers (e.g. unit tests) hit defaults instead. this.type = this.type ?? Packet.TYPE.EDNS; this.class = this.class ?? Packet.EDNS_DEFAULT_UDP_PAYLOAD_SIZE; const ttl = this.ttl ?? 0; this.ttl = ttl; this.extendedRcode = (ttl >>> 24) & 0xff; this.version = (ttl >>> 16) & 0xff; this.doFlag = !!(ttl & 0x8000); this.rdata = []; // RFC 6891 §6.1.2 — RDATA is a sequence of {code, length, data} triples. while (length > 0) { if (length < 4) { throw new Error( `EDNS decode: ${length} octet(s) left in RDATA, too few for an ` + 'option header', ); } const optionCode = reader.read(16); const optionLength = reader.read(16); // In octet (https://tools.ietf.org/html/rfc6891#page-8) length -= 4; if (optionLength > length) { throw new Error( `EDNS decode: option ${optionCode} declares ${optionLength} octet(s) ` + `but only ${length} remain in RDATA`, ); } const decoder = Packet.EDNS_OPTION_NAME[optionCode]; const codec = decoder && Packet.Resource.EDNS[decoder]; if (codec && codec.decode) { const optionEnd = reader.offset + optionLength * 8; this.rdata.push(codec.decode(reader, optionLength)); // An option decoder that mis-counts would shift every option after it. reader.offset = optionEnd; } else { // Skip the option body; `read` counts bits, the option length is octets. reader.offset += optionLength * 8; debug( 'node-dns > skipping EDNS option code %d (%d octets): no decoder', optionCode, optionLength, ); } length -= optionLength; } return this; }; Packet.Resource.EDNS.encode = function (record, writer) { writer = writer || new Packet.Writer(); // RDLENGTH is owned by Packet.Resource.encode; emit option records back to // back into the main writer. for (const rdata of record.rdata) { const encoder = Packet.EDNS_OPTION_NAME[rdata.ednsCode]; const codec = encoder && Packet.Resource.EDNS[encoder]; if (codec && codec.encode) { const w = new Packet.Writer(); codec.encode(rdata, w); // The 16-bit length has to match what the encoder actually wrote. A // fractional or oversized count is silently truncated by write(), which // would misalign every option after this one and the records beyond. if (w.bitLength() % 8 !== 0) { throw new Error( `EDNS option ${rdata.ednsCode} encoder wrote ${w.bitLength()} bits, ` + 'not a whole number of octets', ); } if (w.byteLength() > 0xffff) { throw new Error( `EDNS option ${rdata.ednsCode} is ${w.byteLength()} octets, too long ` + 'for its 16-bit length field', ); } writer.write(rdata.ednsCode, 16); writer.write(w.byteLength(), 16); writer.writeBuffer(w); } else { debug( 'node-dns > dropping EDNS option code %s: no encoder', inspect(rdata.ednsCode), ); } } }; Packet.Resource.EDNS.ECS = function (clientIp) { const [ip, prefixLength] = clientIp.split('/'); const numPrefixLength = parseInt(prefixLength) || 32; return { ednsCode: Packet.EDNS_OPTION_CODE.ECS, family: 1, sourcePrefixLength: numPrefixLength, scopePrefixLength: 0, ip, }; }; Packet.Resource.EDNS.ECS.decode = function (reader, length) { // RFC 7871 §6 — family (2), source prefix (1), scope prefix (1), then the // leftmost ceil(sourcePrefixLength / 8) octets of the address. if (length < 4) { throw new Error( `EDNS.ECS decode: option is ${length} octet(s), expected at least 4`, ); } const rdata = {}; rdata.ednsCode = Packet.EDNS_OPTION_CODE.ECS; rdata.family = reader.read(16); rdata.sourcePrefixLength = reader.read(8); rdata.scopePrefixLength = reader.read(8); length -= 4; const addressOctets = { 1: 4, 2: 16 }[rdata.family]; if (addressOctets !== undefined && length > addressOctets) { throw new Error( `EDNS.ECS decode: family ${rdata.family} address is ${length} octet(s), ` + `at most ${addressOctets}`, ); } if (rdata.family === 1) { const ipv4Octets = []; while (length--) { const octet = reader.read(8); ipv4Octets.push(octet); } while (ipv4Octets.length < 4) { ipv4Octets.push(0); } rdata.ip = ipv4Octets.join('.'); } if (rdata.family === 2) { const ipv6Segments = []; // A truncated address can leave an odd octet; `length > 0` keeps the // countdown from stepping past zero and reading into the next option. for (; length > 0; length -= 2) { const segment = reader.read(16).toString(16); ipv6Segments.push(segment); } while (ipv6Segments.length < 8) { ipv6Segments.push('0'); } rdata.ip = ipv6Segments.join(':'); } return rdata; }; Packet.Resource.EDNS.ECS.encode = function (record, writer) { // RFC 7871 §6: the ADDRESS field carries only the leftmost // ceil(sourcePrefixLength / 8) octets. const octets = Math.ceil(record.sourcePrefixLength / 8); writer.write(record.family, 16); writer.write(record.sourcePrefixLength, 8); writer.write(record.scopePrefixLength, 8); let bytes; if (record.family === 1) { bytes = record.ip.split('.').map(s => parseInt(s, 10) || 0); } else if (record.family === 2) { bytes = expandIPv6ToBytes(record.ip); } else { throw new Error(`EDNS.ECS encode: unsupported family ${record.family}`); } for (let i = 0; i < octets; i++) { writer.write(bytes[i] || 0, 8); } }; // Expand a (possibly compressed) IPv6 text address into a 16-byte array. function expandIPv6ToBytes(address) { let head, tail; const idx = address.indexOf('::'); if (idx === -1) { head = address.split(':'); tail = []; } else { head = address.slice(0, idx).split(':').filter(Boolean); tail = address .slice(idx + 2) .split(':') .filter(Boolean); } const missing = 8 - head.length - tail.length; const groups = [...head, ...new Array(missing).fill('0'), ...tail]; const out = new Array(16).fill(0); for (let g = 0; g < 8; g++) { const n = parseInt(groups[g], 16) || 0; out[g * 2] = (n >> 8) & 0xff; out[g * 2 + 1] = n & 0xff; } return out; } // RFC 8914 §3 — EXTRA-TEXT should stay short. It shares the response with the // answer, which still has to fit the negotiated UDP payload size. Packet.EDE_MAX_TEXT = 256; /** * Extended DNS Error — an INFO-CODE naming the category of failure plus * free-form UTF-8 text explaining it. Additive: it annotates a response * without changing its RCODE. * @docs https://tools.ietf.org/html/rfc8914 */ Packet.Resource.EDNS.EDE = function (infoCode, extraText = '') { return { ednsCode: Packet.EDNS_OPTION_CODE.EDE, infoCode, extraText, }; }; Packet.Resource.EDNS.EDE.decode = function (reader, length) { // RFC 8914 §2 — 16-bit INFO-CODE, then optional EXTRA-TEXT to the end of // the option. if (length < 2) { throw new Error( `EDNS.EDE decode: option is ${length} octet(s), expected at least 2`, ); } const infoCode = reader.read(16); const bytes = Buffer.alloc(length - 2); for (let i = 0; i < bytes.length; i++) bytes[i] = reader.read(8); return { ednsCode: Packet.EDNS_OPTION_CODE.EDE, infoCode, // §3 warns that EXTRA-TEXT must not be assumed null-terminated; senders // that terminate it anyway would otherwise leave NULs in the string. extraText: bytes.toString('utf8').replace(/\0+$/, ''), }; }; Packet.Resource.EDNS.EDE.encode = function (record, writer) { assertCode(record.infoCode, 'infoCode', 'EDNS.EDE encode'); writer.write(record.infoCode, 16); for (const byte of Buffer.from(record.extraText || '', 'utf8')) { writer.write(byte, 8); } }; Packet.Resource.CAA = { encode: function (record, writer) { writer = writer || new Packet.Writer(); // RDLENGTH is written by Packet.Resource.encode. const buffer = Buffer.from(record.tag + record.value, 'utf8'); writer.write(record.flags, 8); writer.write(record.tag.length, 8); buffer.forEach(function (c) { writer.write(c, 8); }); }, decode: function (reader, length) { // RFC 8659 §4.1 — flags octet, tag length octet, then tag and value. if (length < 2) { throw new Error(`CAA decode: RDLENGTH is ${length}, expected at least 2`); } this.flags = reader.read(8); const tagLength = reader.read(8); let remaining = length - 2; if (tagLength > remaining) { throw new Error( `CAA decode: tag length ${tagLength} overruns RDATA ` + `(${remaining} octets remaining)`, ); } const bytes = []; while (remaining--) bytes.push(reader.read(8)); const buffer = Buffer.from(bytes); this.tag = buffer.slice(0, tagLength).toString('utf8'); this.value = buffer.slice(tagLength).toString('utf8'); return this; }, }; /** * @type {{decode: (function(*, *): Packet.Resource.DNSKEY)}} * @link https://tools.ietf.org/html/rfc4034 * @link https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml#table-dns-sec-alg-numbers-1 */ Packet.Resource.DNSKEY = { decode: function (reader, length) { // RFC 4034 §2.1 — flags (2), protocol (1), algorithm (1), then the key. if (length < 4) { throw new Error( `DNSKEY decode: RDLENGTH is ${length}, expected at least 4`, ); } const RData = []; while (RData.length < length) { RData.push(reader.read(8)); } this.flags = (RData[0] << 8) | RData[1]; this.protocol = RData[2]; this.algorithm = RData[3]; // for key tag let ac = 0; for (let i = 0; i < length; ++i) { ac += i & 1 ? RData[i] : RData[i] << 8; } ac += (ac >> 16) & 0xffff; this.keyTag = ac & 0xffff; // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 = 16 // convert binary flags let binFlags = this.flags.toString(2); // add left padding until 16 chars while (binFlags.length < 16) { binFlags = '0' + binFlags; } this.zoneKey = binFlags[7] === '1'; this.zoneSep = binFlags[15] === '1'; this.key = Buffer.from(RData.slice(4)).toString('base64'); return this; }, encode: function (record, writer) { writer = writer || new Packet.Writer(); // RDLENGTH is written by Packet.Resource.encode. const buffer = Buffer.from(record.key, 'base64'); writer.write(record.flags, 16); writer.write(record.protocol, 8); writer.write(record.algorithm, 8); buffer.forEach(function (c) { writer.write(c, 8); }); }, }; /** * RRSIG just support decode * test with dns.resolveRRSIG('example.com') * * @type {{decode: (function(*, *): Packet.Resource.RRSIG)}} */ Packet.Resource.RRSIG = { decode: function (reader, length) { // RFC 4034 §3.2 — inception/expiration are presented as YYYYMMDDHHmmSS in // UTC. Every field has to come from the UTC accessors: mixing in the local // year puts the wrong year on a signature near a year boundary. function dateForSig(seconds) { const date = new Date(seconds * 1000); const pad = n => String(n).padStart(2, '0'); return [ date.getUTCFullYear(), pad(date.getUTCMonth() + 1), pad(date.getUTCDate()), pad(date.getUTCHours()), pad(date.getUTCMinutes()), pad(date.getUTCSeconds()), ].join(''); } // RFC 4034 §3.1 — 18 octets of fixed fields, then the signer name and the // signature. Anything shorter cannot hold a signature. if (length < 18) { throw new Error( `RRSIG decode: RDLENGTH is ${length}, expected at least 18`, ); } const rdataStart = reader.offset; const maxOffset = reader.offset + length * 8; /* * Stuff sign contains 18 octets */ this.sigType = reader.read(16); // 2 this.algorithm = reader.read(8); // 1 this.labels = reader.read(8); // 1 this.originalTtl = reader.read(32); // 4 this.expiration = dateForSig(reader.read(32)); // 4 this.inception = dateForSig(reader.read(32)); // 4 this.keyTag = reader.read(16); // 2 this.signer = Packet.Name.decode(reader); const maxLength = (maxOffset - reader.offset) / 8; const signature = []; while (signature.length < maxLength) { signature.push(reader.read(8)); } this.signature = Buffer.from(signature).toString('base64'); // There is no RRSIG encoder — the decoded form is lossy (timestamps become // display strings). Retaining the raw rdata lets Packet.Resource.encode's // unknown-type fallback re-emit the record byte for byte, so a proxy that // parses and re-serializes a signed response does not strip the signature. this.data = Buffer.from( reader.buffer.subarray(rdataStart / 8, maxOffset / 8), ); return this; }, }; Packet.Reader = BufferReader; Packet.Writer = BufferWriter; Packet.createResponseFromRequest = function (request) { const response = new Packet(); response.header = new Packet.Header({ id: request.header.id, opcode: request.header.opcode, rd: request.header.rd, qr: 1, }); response.questions = request.questions.slice(); return response; }; Packet.createResourceFromQuestion = function (base, record) { const resource = new Packet.Resource(base); Object.assign(resource, record); return resource; }; /** * Build an error response for a request, optionally explaining why with an * RFC 8914 Extended DNS Error. * * For a request that only partly decoded, the reason is already to hand: * * Packet.createErrorResponseFromRequest(request, Packet.RCODE.FORMERR, { * infoCode: Packet.EDE.INVALID_DATA, * extraText: request.errors.map(e => e.message).join('; '), * }); * * @param {Packet} request * @param {number} rcode * @param {{infoCode: number, extraText?: string}} [ede] * @return {Packet} */ Packet.createErrorResponseFromRequest = function (request, rcode, ede) { const response = Packet.createResponseFromRequest(request); response.header.rcode = rcode; const requestOpt = (request.additionals || []).find( r => r && r.type === Packet.TYPE.EDNS, ); // An OPT belongs in the response when the request signalled EDNS // (RFC 6891 §6.1.1), and is *required* for an RCODE above 15, whose high // byte rides in the OPT TTL — without one only the low nibble survives, and // BADVERS would go out as NOERROR. if (!requestOpt && rcode <= 0xf) return response; const rdata = []; // RFC 8914 §3: exte