summaryrefslogtreecommitdiff
path: root/tests/node_compat/test/common/dns.js
diff options
context:
space:
mode:
Diffstat (limited to 'tests/node_compat/test/common/dns.js')
-rw-r--r--tests/node_compat/test/common/dns.js327
1 files changed, 327 insertions, 0 deletions
diff --git a/tests/node_compat/test/common/dns.js b/tests/node_compat/test/common/dns.js
new file mode 100644
index 000000000..54df6a55e
--- /dev/null
+++ b/tests/node_compat/test/common/dns.js
@@ -0,0 +1,327 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 18.12.1
+// This file is automatically generated by `tools/node_compat/setup.ts`. Do not modify this file manually.
+
+'use strict';
+
+const assert = require('assert');
+const os = require('os');
+
+const types = {
+ A: 1,
+ AAAA: 28,
+ NS: 2,
+ CNAME: 5,
+ SOA: 6,
+ PTR: 12,
+ MX: 15,
+ TXT: 16,
+ ANY: 255,
+ CAA: 257,
+};
+
+const classes = {
+ IN: 1,
+};
+
+// Naïve DNS parser/serializer.
+
+function readDomainFromPacket(buffer, offset) {
+ assert.ok(offset < buffer.length);
+ const length = buffer[offset];
+ if (length === 0) {
+ return { nread: 1, domain: '' };
+ } else if ((length & 0xC0) === 0) {
+ offset += 1;
+ const chunk = buffer.toString('ascii', offset, offset + length);
+ // Read the rest of the domain.
+ const { nread, domain } = readDomainFromPacket(buffer, offset + length);
+ return {
+ nread: 1 + length + nread,
+ domain: domain ? `${chunk}.${domain}` : chunk,
+ };
+ }
+ // Pointer to another part of the packet.
+ assert.strictEqual(length & 0xC0, 0xC0);
+ // eslint-disable-next-line space-infix-ops, space-unary-ops
+ const pointeeOffset = buffer.readUInt16BE(offset) &~ 0xC000;
+ return {
+ nread: 2,
+ domain: readDomainFromPacket(buffer, pointeeOffset),
+ };
+}
+
+function parseDNSPacket(buffer) {
+ assert.ok(buffer.length > 12);
+
+ const parsed = {
+ id: buffer.readUInt16BE(0),
+ flags: buffer.readUInt16BE(2),
+ };
+
+ const counts = [
+ ['questions', buffer.readUInt16BE(4)],
+ ['answers', buffer.readUInt16BE(6)],
+ ['authorityAnswers', buffer.readUInt16BE(8)],
+ ['additionalRecords', buffer.readUInt16BE(10)],
+ ];
+
+ let offset = 12;
+ for (const [ sectionName, count ] of counts) {
+ parsed[sectionName] = [];
+ for (let i = 0; i < count; ++i) {
+ const { nread, domain } = readDomainFromPacket(buffer, offset);
+ offset += nread;
+
+ const type = buffer.readUInt16BE(offset);
+
+ const rr = {
+ domain,
+ cls: buffer.readUInt16BE(offset + 2),
+ };
+ offset += 4;
+
+ for (const name in types) {
+ if (types[name] === type)
+ rr.type = name;
+ }
+
+ if (sectionName !== 'questions') {
+ rr.ttl = buffer.readInt32BE(offset);
+ const dataLength = buffer.readUInt16BE(offset);
+ offset += 6;
+
+ switch (type) {
+ case types.A:
+ assert.strictEqual(dataLength, 4);
+ rr.address = `${buffer[offset + 0]}.${buffer[offset + 1]}.` +
+ `${buffer[offset + 2]}.${buffer[offset + 3]}`;
+ break;
+ case types.AAAA:
+ assert.strictEqual(dataLength, 16);
+ rr.address = buffer.toString('hex', offset, offset + 16)
+ .replace(/(.{4}(?!$))/g, '$1:');
+ break;
+ case types.TXT:
+ {
+ let position = offset;
+ rr.entries = [];
+ while (position < offset + dataLength) {
+ const txtLength = buffer[offset];
+ rr.entries.push(buffer.toString('utf8',
+ position + 1,
+ position + 1 + txtLength));
+ position += 1 + txtLength;
+ }
+ assert.strictEqual(position, offset + dataLength);
+ break;
+ }
+ case types.MX:
+ {
+ rr.priority = buffer.readInt16BE(buffer, offset);
+ offset += 2;
+ const { nread, domain } = readDomainFromPacket(buffer, offset);
+ rr.exchange = domain;
+ assert.strictEqual(nread, dataLength);
+ break;
+ }
+ case types.NS:
+ case types.CNAME:
+ case types.PTR:
+ {
+ const { nread, domain } = readDomainFromPacket(buffer, offset);
+ rr.value = domain;
+ assert.strictEqual(nread, dataLength);
+ break;
+ }
+ case types.SOA:
+ {
+ const mname = readDomainFromPacket(buffer, offset);
+ const rname = readDomainFromPacket(buffer, offset + mname.nread);
+ rr.nsname = mname.domain;
+ rr.hostmaster = rname.domain;
+ const trailerOffset = offset + mname.nread + rname.nread;
+ rr.serial = buffer.readUInt32BE(trailerOffset);
+ rr.refresh = buffer.readUInt32BE(trailerOffset + 4);
+ rr.retry = buffer.readUInt32BE(trailerOffset + 8);
+ rr.expire = buffer.readUInt32BE(trailerOffset + 12);
+ rr.minttl = buffer.readUInt32BE(trailerOffset + 16);
+
+ assert.strictEqual(trailerOffset + 20, dataLength);
+ break;
+ }
+ default:
+ throw new Error(`Unknown RR type ${rr.type}`);
+ }
+ offset += dataLength;
+ }
+
+ parsed[sectionName].push(rr);
+
+ assert.ok(offset <= buffer.length);
+ }
+ }
+
+ assert.strictEqual(offset, buffer.length);
+ return parsed;
+}
+
+function writeIPv6(ip) {
+ const parts = ip.replace(/^:|:$/g, '').split(':');
+ const buf = Buffer.alloc(16);
+
+ let offset = 0;
+ for (const part of parts) {
+ if (part === '') {
+ offset += 16 - 2 * (parts.length - 1);
+ } else {
+ buf.writeUInt16BE(parseInt(part, 16), offset);
+ offset += 2;
+ }
+ }
+
+ return buf;
+}
+
+function writeDomainName(domain) {
+ return Buffer.concat(domain.split('.').map((label) => {
+ assert(label.length < 64);
+ return Buffer.concat([
+ Buffer.from([label.length]),
+ Buffer.from(label, 'ascii'),
+ ]);
+ }).concat([Buffer.alloc(1)]));
+}
+
+function writeDNSPacket(parsed) {
+ const buffers = [];
+ const kStandardResponseFlags = 0x8180;
+
+ buffers.push(new Uint16Array([
+ parsed.id,
+ parsed.flags === undefined ? kStandardResponseFlags : parsed.flags,
+ parsed.questions && parsed.questions.length,
+ parsed.answers && parsed.answers.length,
+ parsed.authorityAnswers && parsed.authorityAnswers.length,
+ parsed.additionalRecords && parsed.additionalRecords.length,
+ ]));
+
+ for (const q of parsed.questions) {
+ assert(types[q.type]);
+ buffers.push(writeDomainName(q.domain));
+ buffers.push(new Uint16Array([
+ types[q.type],
+ q.cls === undefined ? classes.IN : q.cls,
+ ]));
+ }
+
+ for (const rr of [].concat(parsed.answers,
+ parsed.authorityAnswers,
+ parsed.additionalRecords)) {
+ if (!rr) continue;
+
+ assert(types[rr.type]);
+ buffers.push(writeDomainName(rr.domain));
+ buffers.push(new Uint16Array([
+ types[rr.type],
+ rr.cls === undefined ? classes.IN : rr.cls,
+ ]));
+ buffers.push(new Int32Array([rr.ttl]));
+
+ const rdLengthBuf = new Uint16Array(1);
+ buffers.push(rdLengthBuf);
+
+ switch (rr.type) {
+ case 'A':
+ rdLengthBuf[0] = 4;
+ buffers.push(new Uint8Array(rr.address.split('.')));
+ break;
+ case 'AAAA':
+ rdLengthBuf[0] = 16;
+ buffers.push(writeIPv6(rr.address));
+ break;
+ case 'TXT': {
+ const total = rr.entries.map((s) => s.length).reduce((a, b) => a + b);
+ // Total length of all strings + 1 byte each for their lengths.
+ rdLengthBuf[0] = rr.entries.length + total;
+ for (const txt of rr.entries) {
+ buffers.push(new Uint8Array([Buffer.byteLength(txt)]));
+ buffers.push(Buffer.from(txt));
+ }
+ break;
+ }
+ case 'MX':
+ rdLengthBuf[0] = 2;
+ buffers.push(new Uint16Array([rr.priority]));
+ // fall through
+ case 'NS':
+ case 'CNAME':
+ case 'PTR':
+ {
+ const domain = writeDomainName(rr.exchange || rr.value);
+ rdLengthBuf[0] += domain.length;
+ buffers.push(domain);
+ break;
+ }
+ case 'SOA':
+ {
+ const mname = writeDomainName(rr.nsname);
+ const rname = writeDomainName(rr.hostmaster);
+ rdLengthBuf[0] = mname.length + rname.length + 20;
+ buffers.push(mname, rname);
+ buffers.push(new Uint32Array([
+ rr.serial, rr.refresh, rr.retry, rr.expire, rr.minttl,
+ ]));
+ break;
+ }
+ case 'CAA':
+ {
+ rdLengthBuf[0] = 5 + rr.issue.length + 2;
+ buffers.push(Buffer.from([Number(rr.critical)]));
+ buffers.push(Buffer.from([Number(5)]));
+ buffers.push(Buffer.from('issue' + rr.issue));
+ break;
+ }
+ default:
+ throw new Error(`Unknown RR type ${rr.type}`);
+ }
+ }
+
+ return Buffer.concat(buffers.map((typedArray) => {
+ const buf = Buffer.from(typedArray.buffer,
+ typedArray.byteOffset,
+ typedArray.byteLength);
+ if (os.endianness() === 'LE') {
+ if (typedArray.BYTES_PER_ELEMENT === 2) buf.swap16();
+ if (typedArray.BYTES_PER_ELEMENT === 4) buf.swap32();
+ }
+ return buf;
+ }));
+}
+
+const mockedErrorCode = 'ENOTFOUND';
+const mockedSysCall = 'getaddrinfo';
+
+function errorLookupMock(code = mockedErrorCode, syscall = mockedSysCall) {
+ return function lookupWithError(hostname, dnsopts, cb) {
+ const err = new Error(`${syscall} ${code} ${hostname}`);
+ err.code = code;
+ err.errno = code;
+ err.syscall = syscall;
+ err.hostname = hostname;
+ cb(err);
+ };
+}
+
+module.exports = {
+ types,
+ classes,
+ writeDNSPacket,
+ parseDNSPacket,
+ errorLookupMock,
+ mockedErrorCode,
+ mockedSysCall,
+};