2023-02-20 10:35:04 -05:00
|
|
|
// deno-fmt-ignore-file
|
|
|
|
// deno-lint-ignore-file
|
|
|
|
|
|
|
|
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
|
|
|
// Taken from Node 18.12.1
|
2023-06-02 02:05:32 -04:00
|
|
|
// This file is automatically generated by `tools/node_compat/setup.ts`. Do not modify this file manually.
|
2023-02-20 10:35:04 -05:00
|
|
|
|
|
|
|
'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
|
|
|
|
};
|