1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-15 03:48:02 -05:00
denoland-deno/cli/tests/node_compat/test/common/dns.js

328 lines
9.2 KiB
JavaScript
Raw Normal View History

// 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,
};