Add comprehensive installation and setup documentation

- Add GETTING_STARTED.md with quick start guide and development modes
- Add INSTALL.sh automated installation script
- Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md
- Add QUICK_REFERENCE.md for common commands
- Add SETUP_GUIDE.md with detailed setup instructions
- Update README.md with improved project overview
- Add did-wallet app dependencies and node_modules
This commit is contained in:
Dorian
2026-01-27 17:18:21 +00:00
parent a81f655133
commit 0d073fa89e
22658 changed files with 4494151 additions and 6 deletions
+44
View File
@@ -0,0 +1,44 @@
# Version 6.0.0 - 2022-05-31
- Breaking: Node.js Buffer is not used by the library anymore and Uint8Array instances will be returned
by default instead of it.
# Version 5.4.0 - 2022-05-27
- Feature: Adding dedicated packet codec
- Feature: Adding dedicated query/response codec as multiple queries are only theoretically supported.
# Version 5.3.0 - 2022-05-22
- Refactor: ESM module support
# Version 5.2.0 - 2019-02-21
- Feature: Added support for de/encoding certain OPT options.
# Version 5.1.0 - 2019-01-22
- Feature: Added support for the RP record type.
# Version 5.0.0 - 2018-06-01
- Breaking: Node.js 6.0.0 or greater is now required.
- Feature: Added support for DNSSEC record types.
# Version 4.1.0 - 2018-02-11
- Feature: Added support for the MX record type.
# Version 4.0.0 - 2018-02-04
- Feature: Added `streamEncode` and `streamDecode` methods for encoding TCP packets.
- Breaking: Changed the decoded value of TXT records to an array of Buffers. This is to accomodate DNS-SD records which rely on the individual strings record being separated.
- Breaking: Renamed the `flag_trunc` and `flag_auth` to `flag_tc` and `flag_aa` to match the names of these in the dns standards.
# Version 3.0.0 - 2018-01-12
- Breaking: The `class` option has been changed from integer to string.
# Version 2.0.0 - 2018-01-11
- Breaking: Converted module to ES2015, now requires Node.js 4.0 or greater
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Mathias Buus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+382
View File
@@ -0,0 +1,382 @@
# @dnsquery/dns-packet
[![](https://img.shields.io/npm/v/@dnsquery/dns-packet.svg?style=flat)](https://www.npmjs.org/package/@dnsquery/dns-packet) [![](https://img.shields.io/npm/dm/@dnsquery/dns-packet.svg)](https://www.npmjs.org/package/@dnsquery/dns-packet) [![Tests](https://github.com/dnsquery/dns-packet/actions/workflows/test.yml/badge.svg)](https://github.com/dnsquery/dns-packet/actions/workflows/test.yml)
An [abstract-encoding](https://github.com/mafintosh/abstract-encoding) compliant module for encoding / decoding DNS packets. Lifted out of [multicast-dns](https://github.com/mafintosh/multicast-dns) as a separate module.
**This is a fork** of [dns-packet][] that comes with typescript types, javascript modules and does
not use Node.js primitives. Works well in browsers/react-native.
[dns-packet]: https://github.com/mafintosh/dns-packet
```
npm install @dnsquery/dns-packet
```
## UDP Usage
``` js
import * as dnsPacket from '@dnsquery/dns-packet'
import dgram from 'dgram'
const socket = dgram.createSocket('udp4')
const buf = dnsPacket.encode({
type: 'query',
id: 1,
flags: dnsPacket.RECURSION_DESIRED,
questions: [{
type: 'A',
name: 'google.com'
}]
})
socket.on('message', message => {
console.log(dnsPacket.decode(message)) // prints out a response from google dns
})
socket.send(buf, 0, buf.length, 53, '8.8.8.8')
```
Also see [the UDP example](examples/udp.js).
## TCP, TLS, HTTPS
While DNS has traditionally been used over a datagram transport, it is increasingly being carried over TCP for larger responses commonly including DNSSEC responses and TLS or HTTPS for enhanced security. See below examples on how to use `dns-packet` to wrap DNS packets in these protocols:
- [TCP](examples/tcp.js)
- [DNS over TLS](examples/tls.js)
- [DNS over HTTPS](examples/doh.js)
## API
#### `var buf = packets.encode(packet, [buf], [offset])`
Encodes a DNS packet into a buffer containing a UDP payload.
#### `var packet = packets.decode(buf, [offset])`
Decode a DNS packet from a buffer containing a UDP payload.
#### `var buf = packets.streamEncode(packet, [buf], [offset])`
Encodes a DNS packet into a buffer containing a TCP payload.
#### `var packet = packets.streamDecode(buf, [offset])`
Decode a DNS packet from a buffer containing a TCP payload.
#### `var len = packets.encodingLength(packet)`
Returns how many bytes are needed to encode the DNS packet
## Packets
Packets look like this
``` js
{
type: 'query|response',
id: optionalIdNumber,
flags: optionalBitFlags,
questions: [...],
answers: [...],
additionals: [...],
authorities: [...]
}
```
The bit flags available are
``` js
import {
RECURSION_DESIRED,
RECURSION_AVAILABLE,
TRUNCATED_RESPONSE,
AUTHORITATIVE_ANSWER,
AUTHENTIC_DATA,
CHECKING_DISABLED
} from '@dnsquery/dns-packet'
```
To use more than one flag bitwise-or them together
``` js
var flags = packet.RECURSION_DESIRED | packet.RECURSION_AVAILABLE
```
And to check for a flag use bitwise-and
``` js
var isRecursive = message.flags & packet.RECURSION_DESIRED
```
A question looks like this
``` js
{
type: 'A', // or SRV, AAAA, etc
class: 'IN', // one of IN, CS, CH, HS, ANY. Default: IN
name: 'google.com' // which record are you looking for
}
```
And an answer, additional, or authority looks like this
``` js
{
type: 'A', // or SRV, AAAA, etc
class: 'IN', // one of IN, CS, CH, HS
name: 'google.com', // which name is this record for
ttl: optionalTimeToLiveInSeconds,
(record specific data, see below)
}
```
## Supported record types
#### `A`
``` js
{
data: 'IPv4 address' // fx 127.0.0.1
}
```
#### `AAAA`
``` js
{
data: 'IPv6 address' // fx fe80::1
}
```
#### `CAA`
``` js
{
flags: 128, // octet
tag: 'issue|issuewild|iodef',
value: 'ca.example.net',
issuerCritical: false
}
```
#### `CNAME`
``` js
{
data: 'cname.to.another.record'
}
```
#### `DNAME`
``` js
{
data: 'dname.to.another.record'
}
```
#### `DNSKEY`
``` js
{
flags: 257, // 16 bits
algorithm: 1, // octet
key: Buffer
}
```
#### `DS`
``` js
{
keyTag: 12345,
algorithm: 8,
digestType: 1,
digest: Buffer
}
```
#### `HINFO`
``` js
{
data: {
cpu: 'cpu info',
os: 'os info'
}
}
```
#### `MX`
``` js
{
preference: 10,
exchange: 'mail.example.net'
}
```
#### `NS`
``` js
{
data: nameServer
}
```
#### `NSEC`
``` js
{
nextDomain: 'a.domain',
rrtypes: ['A', 'TXT', 'RRSIG']
}
```
#### `NSEC3`
``` js
{
algorithm: 1,
flags: 0,
iterations: 2,
salt: Buffer,
nextDomain: Buffer, // Hashed per RFC5155
rrtypes: ['A', 'TXT', 'RRSIG']
}
```
#### `NULL`
``` js
{
data: Buffer('any binary data')
}
```
#### `OPT`
[EDNS0](https://tools.ietf.org/html/rfc6891) options.
``` js
{
type: 'OPT',
name: '.',
udpPayloadSize: 4096,
flags: packet.DNSSEC_OK,
options: [{
// pass in any code/data for generic EDNS0 options
code: 12,
data: Buffer.alloc(31)
}, {
// Several EDNS0 options have enhanced support
code: 'PADDING',
length: 31,
}, {
code: 'CLIENT_SUBNET',
family: 2, // 1 for IPv4, 2 for IPv6
sourcePrefixLength: 64, // used to truncate IP address
scopePrefixLength: 0,
ip: 'fe80::',
}, {
code: 'TCP_KEEPALIVE',
timeout: 150 // increments of 100ms. This means 15s.
}, {
code: 'KEY_TAG',
tags: [1, 2, 3],
}]
}
```
The options `PADDING`, `CLIENT_SUBNET`, `TCP_KEEPALIVE` and `KEY_TAG` support enhanced de/encoding. See [optionscodes.js](https://github.com/mafintosh/dns-packet/blob/master/optioncodes.js) for all supported option codes. If the `data` property is present on a option, it takes precedence. On decoding, `data` will always be defined.
#### `PTR`
``` js
{
data: 'points.to.another.record'
}
```
#### `RP`
``` js
{
mbox: 'admin.example.com',
txt: 'txt.example.com'
}
```
#### `SSHFP`
``` js
{
algorithm: 1,
hash: 1,
fingerprint: 'A108C9F834354D5B37AF988141C9294822F5BC00'
}
````
#### `RRSIG`
``` js
{
typeCovered: 'A',
algorithm: 8,
labels: 1,
originalTTL: 3600,
expiration: timestamp,
inception: timestamp,
keyTag: 12345,
signersName: 'a.name',
signature: Buffer
}
```
#### `SOA`
``` js
{
data:
{
mname: domainName,
rname: mailbox,
serial: zoneSerial,
refresh: refreshInterval,
retry: retryInterval,
expire: expireInterval,
minimum: minimumTTL
}
}
```
#### `SRV`
``` js
{
data: {
port: servicePort,
target: serviceHostName,
priority: optionalServicePriority,
weight: optionalServiceWeight
}
}
```
#### `TXT`
``` js
{
data: 'text' || Buffer || [ Buffer || 'text' ]
}
```
When encoding, scalar values are converted to an array and strings are converted to UTF-8 encoded Buffers. When decoding, the return value will always be an array of Buffer.
If you need another record type, open an issue and we'll try to add it.
## License
MIT
+12
View File
@@ -0,0 +1,12 @@
export function isU8Arr (input: any): input is Uint8Array
export function bytelength (input: string | Uint8Array): number
export function from (input: Uint8Array | Array | string): Uint8Array
export function write (arr: Uint8Array, str: string, start: number): number
export function toHex (buf: Uint8Array, start?: number, end?: number): string
export function hexLength (str: string): number
export function writeHex (buf: Uint8Array, str: string, start: number, end: number): Uint8Array
export function readUInt32BE (buf: Uint8Array, offset: number): number
export function readUInt16BE (buf: Uint8Array, offset: number): number
export function writeUInt32BE (buf: Uint8Array, value: number, offset: number): number
export function writeUInt16BE (buf: Uint8Array, value: number, offset: number): number
export function copy (source: Uint8Array, target: Uint8Array, targetStart?: number, sourceStart?: number, sourceEnd?: number): number
+133
View File
@@ -0,0 +1,133 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.bytelength = bytelength;
exports.copy = copy;
exports.from = from;
exports.hexLength = hexLength;
exports.readUInt32BE = exports.readUInt16BE = exports.isU8Arr = void 0;
exports.toHex = toHex;
exports.write = write;
exports.writeHex = writeHex;
exports.writeUInt32BE = exports.writeUInt16BE = void 0;
var utf8 = _interopRequireWildcard(require("utf8-codec"), true);
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
const isU8Arr = input => input instanceof Uint8Array;
exports.isU8Arr = isU8Arr;
function bytelength(input) {
return typeof input === 'string' ? utf8.encodingLength(input) : input.byteLength;
}
function from(input) {
if (input instanceof Uint8Array) {
return input;
}
if (Array.isArray(input)) {
return new Uint8Array(input);
}
return utf8.encode(input);
}
function write(arr, str, start) {
if (typeof str !== 'string') {
throw new Error('unknown input type');
}
utf8.encode(str, arr, start);
return utf8.encode.bytes;
}
const hexNum = {};
const numHex = new Array(0xff);
for (let b0 = 0; b0 <= 0xf; b0 += 1) {
const b0L = b0.toString(16);
const b0U = b0L.toUpperCase();
for (let b1 = 0; b1 <= 0xf; b1 += 1) {
const b1L = b1.toString(16);
const b1U = b1L.toUpperCase();
const num = b0 << 4 | b1;
const hex = `${b0L}${b1L}`;
numHex[num] = hex;
hexNum[hex] = num;
hexNum[`${b0U}${b1L}`] = num;
hexNum[`${b0L}${b1U}`] = num;
hexNum[`${b0U}${b1U}`] = num;
}
}
function toHex(buf, start, end) {
let result = '';
for (let offset = start; offset < end;) {
const num = buf[offset++];
result += numHex[num];
}
return result;
}
function hexLength(string) {
return string.length >>> 1;
}
function writeHex(buf, string, offset, end) {
let i = 0;
while (offset < end) {
const hex = string.substr(i, 2);
const num = hexNum[hex];
if (num === undefined) return;
buf[offset++] = num;
i += 2;
}
return buf;
}
const P_24 = Math.pow(2, 24);
const P_16 = Math.pow(2, 16);
const P_8 = Math.pow(2, 8);
const readUInt32BE = (buf, offset) => buf[offset] * P_24 + buf[offset + 1] * P_16 + buf[offset + 2] * P_8 + buf[offset + 3];
exports.readUInt32BE = readUInt32BE;
const readUInt16BE = (buf, offset) => buf[offset] << 8 | buf[offset + 1];
exports.readUInt16BE = readUInt16BE;
const writeUInt32BE = (buf, value, offset) => {
value = +value;
buf[offset + 3] = value;
value = value >>> 8;
buf[offset + 2] = value;
value = value >>> 8;
buf[offset + 1] = value;
value = value >>> 8;
buf[offset] = value;
return offset + 4;
};
exports.writeUInt32BE = writeUInt32BE;
const writeUInt16BE = (buf, value, offset) => {
buf[offset] = value >> 8;
buf[offset + 1] = value & 0xFF;
return offset + 2;
};
exports.writeUInt16BE = writeUInt16BE;
function copy(source, target, targetStart, sourceStart, sourceEnd) {
if (targetStart < 0) {
sourceStart -= targetStart;
targetStart = 0;
}
if (sourceStart < 0) {
sourceStart = 0;
}
if (sourceEnd < 0) {
return new Uint8Array(0);
}
if (targetStart >= target.length || sourceStart >= sourceEnd) {
return 0;
}
return _copyActual(source, target, targetStart, sourceStart, sourceEnd);
}
function _copyActual(source, target, targetStart, sourceStart, sourceEnd) {
if (sourceEnd - sourceStart > target.length - targetStart) {
sourceEnd = sourceStart + target.length - targetStart;
}
let nb = sourceEnd - sourceStart;
const sourceLen = source.length - sourceStart;
if (nb > sourceLen) {
nb = sourceLen;
}
if (sourceStart !== 0 || sourceEnd < source.length) {
source = new Uint8Array(source.buffer, source.byteOffset + sourceStart, nb);
}
target.set(source, targetStart);
return nb;
}
+135
View File
@@ -0,0 +1,135 @@
import * as utf8 from 'utf8-codec'
export const isU8Arr = input => input instanceof Uint8Array
export function bytelength (input) {
return typeof input === 'string' ? utf8.encodingLength(input) : input.byteLength
}
export function from (input) {
if (input instanceof Uint8Array) {
return input
}
if (Array.isArray(input)) {
return new Uint8Array(input)
}
return utf8.encode(input)
}
export function write (arr, str, start) {
if (typeof str !== 'string') {
throw new Error('unknown input type')
}
utf8.encode(str, arr, start)
return utf8.encode.bytes
}
const hexNum = {}
const numHex = new Array(0xff)
for (let b0 = 0; b0 <= 0xf; b0 += 1) {
const b0L = b0.toString(16)
const b0U = b0L.toUpperCase()
for (let b1 = 0; b1 <= 0xf; b1 += 1) {
const b1L = b1.toString(16)
const b1U = b1L.toUpperCase()
const num = b0 << 4 | b1
const hex = `${b0L}${b1L}`
numHex[num] = hex
hexNum[hex] = num
hexNum[`${b0U}${b1L}`] = num
hexNum[`${b0L}${b1U}`] = num
hexNum[`${b0U}${b1U}`] = num
}
}
export function toHex (buf, start, end) {
let result = ''
for (let offset = start; offset < end;) {
const num = buf[offset++]
result += numHex[num]
}
return result
}
export function hexLength (string) {
return string.length >>> 1
}
export function writeHex (buf, string, offset, end) {
let i = 0
while (offset < end) {
const hex = string.substr(i, 2)
const num = hexNum[hex]
if (num === undefined) return
buf[offset++] = num
i += 2
}
return buf
}
const P_24 = Math.pow(2, 24)
const P_16 = Math.pow(2, 16)
const P_8 = Math.pow(2, 8)
export const readUInt32BE = (buf, offset) => buf[offset] * P_24 +
buf[offset + 1] * P_16 +
buf[offset + 2] * P_8 +
buf[offset + 3]
export const readUInt16BE = (buf, offset) => (buf[offset] << 8) | buf[offset + 1]
export const writeUInt32BE = (buf, value, offset) => {
value = +value
buf[offset + 3] = value
value = value >>> 8
buf[offset + 2] = value
value = value >>> 8
buf[offset + 1] = value
value = value >>> 8
buf[offset] = value
return offset + 4
}
export const writeUInt16BE = (buf, value, offset) => {
buf[offset] = value >> 8
buf[offset + 1] = value & 0xFF
return offset + 2
}
export function copy (source, target, targetStart, sourceStart, sourceEnd) {
if (targetStart < 0) {
sourceStart -= targetStart
targetStart = 0
}
if (sourceStart < 0) {
sourceStart = 0
}
if (sourceEnd < 0) {
return new Uint8Array(0)
}
if (targetStart >= target.length || sourceStart >= sourceEnd) {
return 0
}
return _copyActual(source, target, targetStart, sourceStart, sourceEnd)
}
function _copyActual (source, target, targetStart, sourceStart, sourceEnd) {
if (sourceEnd - sourceStart > target.length - targetStart) {
sourceEnd = sourceStart + target.length - targetStart
}
let nb = sourceEnd - sourceStart
const sourceLen = source.length - sourceStart
if (nb > sourceLen) {
nb = sourceLen
}
if (sourceStart !== 0 || sourceEnd < source.length) {
source = new Uint8Array(source.buffer, source.byteOffset + sourceStart, nb)
}
target.set(source, targetStart)
return nb
}
+9
View File
@@ -0,0 +1,9 @@
export type Classes = "IN"
| "CS"
| "CH"
| "HS"
| "ANY"
| string;
export function toString (klass: number): Classes;
export function toClass (name: Classes): number;
+37
View File
@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.toClass = toClass;
exports.toString = toString;
function toString(klass) {
switch (klass) {
case 1:
return 'IN';
case 2:
return 'CS';
case 3:
return 'CH';
case 4:
return 'HS';
case 255:
return 'ANY';
}
return 'UNKNOWN_' + klass;
}
function toClass(name) {
switch (name.toUpperCase()) {
case 'IN':
return 1;
case 'CS':
return 2;
case 'CH':
return 3;
case 'HS':
return 4;
case 'ANY':
return 255;
}
return 0;
}
+21
View File
@@ -0,0 +1,21 @@
export function toString (klass) {
switch (klass) {
case 1: return 'IN'
case 2: return 'CS'
case 3: return 'CH'
case 4: return 'HS'
case 255: return 'ANY'
}
return 'UNKNOWN_' + klass
}
export function toClass (name) {
switch (name.toUpperCase()) {
case 'IN': return 1
case 'CS': return 2
case 'CH': return 3
case 'HS': return 4
case 'ANY': return 255
}
return 0
}
+47
View File
@@ -0,0 +1,47 @@
"use strict";
var dnsPacket = _interopRequireWildcard(require("@leichtgewicht/dns-packet"), true);
var _https = require("https");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/*
* Sample code to make DNS over HTTPS request using POST
* AUTHOR: Tom Pusateri <pusateri@bangj.com>
* DATE: March 17, 2018
* LICENSE: MIT
*/
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const buf = dnsPacket.encode({
type: 'query',
id: getRandomInt(1, 65534),
flags: dnsPacket.RECURSION_DESIRED,
questions: [{
type: 'A',
name: 'google.com'
}]
});
const options = {
hostname: 'dns.google',
port: 443,
path: '/dns-query',
method: 'POST',
headers: {
'Content-Type': 'application/dns-message',
'Content-Length': Buffer.byteLength(buf)
}
};
const request = _https.request(options, response => {
console.log('statusCode:', response.statusCode);
console.log('headers:', response.headers);
response.on('data', d => {
console.log(dnsPacket.decode(d));
});
});
request.on('error', e => {
console.error(e);
});
request.write(buf);
request.end();
+49
View File
@@ -0,0 +1,49 @@
/*
* Sample code to make DNS over HTTPS request using POST
* AUTHOR: Tom Pusateri <pusateri@bangj.com>
* DATE: March 17, 2018
* LICENSE: MIT
*/
import * as dnsPacket from '@leichtgewicht/dns-packet'
import https from 'https'
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
const buf = dnsPacket.encode({
type: 'query',
id: getRandomInt(1, 65534),
flags: dnsPacket.RECURSION_DESIRED,
questions: [{
type: 'A',
name: 'google.com'
}]
})
const options = {
hostname: 'dns.google',
port: 443,
path: '/dns-query',
method: 'POST',
headers: {
'Content-Type': 'application/dns-message',
'Content-Length': Buffer.byteLength(buf)
}
}
const request = https.request(options, (response) => {
console.log('statusCode:', response.statusCode)
console.log('headers:', response.headers)
response.on('data', (d) => {
console.log(dnsPacket.decode(d))
})
})
request.on('error', (e) => {
console.error(e)
})
request.write(buf)
request.end()
+47
View File
@@ -0,0 +1,47 @@
"use strict";
var dnsPacket = _interopRequireWildcard(require("@leichtgewicht/dns-packet"), true);
var _net = require("net");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
let response = null;
let expectedLength = 0;
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const buf = dnsPacket.streamEncode({
type: 'query',
id: getRandomInt(1, 65534),
flags: dnsPacket.RECURSION_DESIRED,
questions: [{
type: 'A',
name: 'google.com'
}]
});
const client = new _net.Socket();
client.connect(53, '8.8.8.8', function () {
console.log('Connected');
client.write(buf);
});
client.on('data', function (data) {
console.log('Received response: %d bytes', data.byteLength);
if (response == null) {
if (data.byteLength > 1) {
const plen = data.readUInt16BE(0);
expectedLength = plen;
if (plen < 12) {
throw new Error('below DNS minimum packet length');
}
response = Buffer.from(data);
}
} else {
response = Buffer.concat([response, data]);
}
if (response.byteLength >= expectedLength) {
console.log(dnsPacket.streamDecode(response));
client.destroy();
}
});
client.on('close', function () {
console.log('Connection closed');
});
+50
View File
@@ -0,0 +1,50 @@
import * as dnsPacket from '@leichtgewicht/dns-packet'
import net from 'net'
let response = null
let expectedLength = 0
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
const buf = dnsPacket.streamEncode({
type: 'query',
id: getRandomInt(1, 65534),
flags: dnsPacket.RECURSION_DESIRED,
questions: [{
type: 'A',
name: 'google.com'
}]
})
const client = new net.Socket()
client.connect(53, '8.8.8.8', function () {
console.log('Connected')
client.write(buf)
})
client.on('data', function (data) {
console.log('Received response: %d bytes', data.byteLength)
if (response == null) {
if (data.byteLength > 1) {
const plen = data.readUInt16BE(0)
expectedLength = plen
if (plen < 12) {
throw new Error('below DNS minimum packet length')
}
response = Buffer.from(data)
}
} else {
response = Buffer.concat([response, data])
}
if (response.byteLength >= expectedLength) {
console.log(dnsPacket.streamDecode(response))
client.destroy()
}
})
client.on('close', function () {
console.log('Connection closed')
})
+54
View File
@@ -0,0 +1,54 @@
"use strict";
var dnsPacket = _interopRequireWildcard(require("@leichtgewicht/dns-packet"), true);
var _tls = require("tls");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
let response = null;
let expectedLength = 0;
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const buf = dnsPacket.streamEncode({
type: 'query',
id: getRandomInt(1, 65534),
flags: dnsPacket.RECURSION_DESIRED,
questions: [{
type: 'A',
name: 'google.com'
}]
});
const context = _tls.createSecureContext({
secureProtocol: 'TLSv1_2_method'
});
const options = {
port: 853,
host: 'getdnsapi.net',
secureContext: context
};
const client = _tls.connect(options, () => {
console.log('client connected');
client.write(buf);
});
client.on('data', function (data) {
console.log('Received response: %d bytes', data.byteLength);
if (response == null) {
if (data.byteLength > 1) {
const plen = data.readUInt16BE(0);
expectedLength = plen;
if (plen < 12) {
throw new Error('below DNS minimum packet length');
}
response = Buffer.from(data);
}
} else {
response = Buffer.concat([response, data]);
}
if (response.byteLength >= expectedLength) {
console.log(dnsPacket.streamDecode(response));
client.destroy();
}
});
client.on('end', () => {
console.log('Connection ended');
});
+59
View File
@@ -0,0 +1,59 @@
import * as dnsPacket from '@leichtgewicht/dns-packet'
import tls from 'tls'
let response = null
let expectedLength = 0
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
const buf = dnsPacket.streamEncode({
type: 'query',
id: getRandomInt(1, 65534),
flags: dnsPacket.RECURSION_DESIRED,
questions: [{
type: 'A',
name: 'google.com'
}]
})
const context = tls.createSecureContext({
secureProtocol: 'TLSv1_2_method'
})
const options = {
port: 853,
host: 'getdnsapi.net',
secureContext: context
}
const client = tls.connect(options, () => {
console.log('client connected')
client.write(buf)
})
client.on('data', function (data) {
console.log('Received response: %d bytes', data.byteLength)
if (response == null) {
if (data.byteLength > 1) {
const plen = data.readUInt16BE(0)
expectedLength = plen
if (plen < 12) {
throw new Error('below DNS minimum packet length')
}
response = Buffer.from(data)
}
} else {
response = Buffer.concat([response, data])
}
if (response.byteLength >= expectedLength) {
console.log(dnsPacket.streamDecode(response))
client.destroy()
}
})
client.on('end', () => {
console.log('Connection ended')
})
+25
View File
@@ -0,0 +1,25 @@
"use strict";
var dnsPacket = _interopRequireWildcard(require("@leichtgewicht/dns-packet"), true);
var _dgram = require("dgram");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
const socket = _dgram.createSocket('udp4');
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const buf = dnsPacket.encode({
type: 'query',
id: getRandomInt(1, 65534),
flags: dnsPacket.RECURSION_DESIRED,
questions: [{
type: 'A',
name: 'google.com'
}]
});
socket.on('message', function (message, rinfo) {
console.log(rinfo);
console.log(dnsPacket.decode(message)); // prints out a response from google dns
socket.close();
});
socket.send(buf, 0, buf.length, 53, '8.8.8.8');
+26
View File
@@ -0,0 +1,26 @@
import * as dnsPacket from '@leichtgewicht/dns-packet'
import dgram from 'dgram'
const socket = dgram.createSocket('udp4')
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
const buf = dnsPacket.encode({
type: 'query',
id: getRandomInt(1, 65534),
flags: dnsPacket.RECURSION_DESIRED,
questions: [{
type: 'A',
name: 'google.com'
}]
})
socket.on('message', function (message, rinfo) {
console.log(rinfo)
console.log(dnsPacket.decode(message)) // prints out a response from google dns
socket.close()
})
socket.send(buf, 0, buf.length, 53, '8.8.8.8')
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+24
View File
@@ -0,0 +1,24 @@
/*
* Traditional DNS header OPCODEs (4-bits) defined by IANA in
* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5
*/
export type OPCode = "QUERY"
| "IQUERY"
| "STATUS"
| "OPCODE_3"
| "NOTIFY"
| "UPDATE"
| "OPCODE_6"
| "OPCODE_7"
| "OPCODE_8"
| "OPCODE_9"
| "OPCODE_10"
| "OPCODE_11"
| "OPCODE_12"
| "OPCODE_13"
| "OPCODE_14"
| "OPCODE_15"
| string;
export function toString (opcode: number): OPCode;
export function toOpcode (code: OPCode): number;
+86
View File
@@ -0,0 +1,86 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.toOpcode = toOpcode;
exports.toString = toString;
/*
* Traditional DNS header OPCODEs (4-bits) defined by IANA in
* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5
*/
function toString(opcode) {
switch (opcode) {
case 0:
return 'QUERY';
case 1:
return 'IQUERY';
case 2:
return 'STATUS';
case 3:
return 'OPCODE_3';
case 4:
return 'NOTIFY';
case 5:
return 'UPDATE';
case 6:
return 'OPCODE_6';
case 7:
return 'OPCODE_7';
case 8:
return 'OPCODE_8';
case 9:
return 'OPCODE_9';
case 10:
return 'OPCODE_10';
case 11:
return 'OPCODE_11';
case 12:
return 'OPCODE_12';
case 13:
return 'OPCODE_13';
case 14:
return 'OPCODE_14';
case 15:
return 'OPCODE_15';
}
return 'OPCODE_' + opcode;
}
function toOpcode(code) {
switch (code.toUpperCase()) {
case 'QUERY':
return 0;
case 'IQUERY':
return 1;
case 'STATUS':
return 2;
case 'OPCODE_3':
return 3;
case 'NOTIFY':
return 4;
case 'UPDATE':
return 5;
case 'OPCODE_6':
return 6;
case 'OPCODE_7':
return 7;
case 'OPCODE_8':
return 8;
case 'OPCODE_9':
return 9;
case 'OPCODE_10':
return 10;
case 'OPCODE_11':
return 11;
case 'OPCODE_12':
return 12;
case 'OPCODE_13':
return 13;
case 'OPCODE_14':
return 14;
case 'OPCODE_15':
return 15;
}
return 0;
}
+48
View File
@@ -0,0 +1,48 @@
/*
* Traditional DNS header OPCODEs (4-bits) defined by IANA in
* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5
*/
export function toString (opcode) {
switch (opcode) {
case 0: return 'QUERY'
case 1: return 'IQUERY'
case 2: return 'STATUS'
case 3: return 'OPCODE_3'
case 4: return 'NOTIFY'
case 5: return 'UPDATE'
case 6: return 'OPCODE_6'
case 7: return 'OPCODE_7'
case 8: return 'OPCODE_8'
case 9: return 'OPCODE_9'
case 10: return 'OPCODE_10'
case 11: return 'OPCODE_11'
case 12: return 'OPCODE_12'
case 13: return 'OPCODE_13'
case 14: return 'OPCODE_14'
case 15: return 'OPCODE_15'
}
return 'OPCODE_' + opcode
}
export function toOpcode (code) {
switch (code.toUpperCase()) {
case 'QUERY': return 0
case 'IQUERY': return 1
case 'STATUS': return 2
case 'OPCODE_3': return 3
case 'NOTIFY': return 4
case 'UPDATE': return 5
case 'OPCODE_6': return 6
case 'OPCODE_7': return 7
case 'OPCODE_8': return 8
case 'OPCODE_9': return 9
case 'OPCODE_10': return 10
case 'OPCODE_11': return 11
case 'OPCODE_12': return 12
case 'OPCODE_13': return 13
case 'OPCODE_14': return 14
case 'OPCODE_15': return 15
}
return 0
}
+18
View File
@@ -0,0 +1,18 @@
export type OptionCodes = "LLQ"
| "UL"
| "NSID"
| "DAU"
| "DHU"
| "N3U"
| "CLIENT_SUBNET"
| "EXPIRE"
| "COOKIE"
| "TCP_KEEPALIVE"
| "PADDING"
| "CHAIN"
| "KEY_TAG"
| "DEVICEID"
| string;
export function toString (type: number): OptionCodes;
export function toCode (name: OptionCodes): number;
+94
View File
@@ -0,0 +1,94 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.toCode = toCode;
exports.toString = toString;
function toString(type) {
switch (type) {
// list at
// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11
case 1:
return 'LLQ';
case 2:
return 'UL';
case 3:
return 'NSID';
case 5:
return 'DAU';
case 6:
return 'DHU';
case 7:
return 'N3U';
case 8:
return 'CLIENT_SUBNET';
case 9:
return 'EXPIRE';
case 10:
return 'COOKIE';
case 11:
return 'TCP_KEEPALIVE';
case 12:
return 'PADDING';
case 13:
return 'CHAIN';
case 14:
return 'KEY_TAG';
case 26946:
return 'DEVICEID';
}
if (type < 0) {
return null;
}
return `OPTION_${type}`;
}
function toCode(name) {
if (typeof name === 'number') {
return name;
}
if (!name) {
return -1;
}
switch (name.toUpperCase()) {
case 'OPTION_0':
return 0;
case 'LLQ':
return 1;
case 'UL':
return 2;
case 'NSID':
return 3;
case 'OPTION_4':
return 4;
case 'DAU':
return 5;
case 'DHU':
return 6;
case 'N3U':
return 7;
case 'CLIENT_SUBNET':
return 8;
case 'EXPIRE':
return 9;
case 'COOKIE':
return 10;
case 'TCP_KEEPALIVE':
return 11;
case 'PADDING':
return 12;
case 'CHAIN':
return 13;
case 'KEY_TAG':
return 14;
case 'DEVICEID':
return 26946;
case 'OPTION_65535':
return 65535;
}
const m = name.match(/_(\d+)$/);
if (m) {
return parseInt(m[1], 10);
}
return -1;
}
+57
View File
@@ -0,0 +1,57 @@
export function toString (type) {
switch (type) {
// list at
// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11
case 1: return 'LLQ'
case 2: return 'UL'
case 3: return 'NSID'
case 5: return 'DAU'
case 6: return 'DHU'
case 7: return 'N3U'
case 8: return 'CLIENT_SUBNET'
case 9: return 'EXPIRE'
case 10: return 'COOKIE'
case 11: return 'TCP_KEEPALIVE'
case 12: return 'PADDING'
case 13: return 'CHAIN'
case 14: return 'KEY_TAG'
case 26946: return 'DEVICEID'
}
if (type < 0) {
return null
}
return `OPTION_${type}`
}
export function toCode (name) {
if (typeof name === 'number') {
return name
}
if (!name) {
return -1
}
switch (name.toUpperCase()) {
case 'OPTION_0': return 0
case 'LLQ': return 1
case 'UL': return 2
case 'NSID': return 3
case 'OPTION_4': return 4
case 'DAU': return 5
case 'DHU': return 6
case 'N3U': return 7
case 'CLIENT_SUBNET': return 8
case 'EXPIRE': return 9
case 'COOKIE': return 10
case 'TCP_KEEPALIVE': return 11
case 'PADDING': return 12
case 'CHAIN': return 13
case 'KEY_TAG': return 14
case 'DEVICEID': return 26946
case 'OPTION_65535': return 65535
}
const m = name.match(/_(\d+)$/)
if (m) {
return parseInt(m[1], 10)
}
return -1
}
+80
View File
@@ -0,0 +1,80 @@
{
"name": "@dnsquery/dns-packet",
"version": "6.1.1",
"description": "An abstract-encoding compliant module for encoding / decoding DNS packets",
"author": "Mathias Buus",
"license": "MIT",
"repository": "dnsquery/dns-packet",
"homepage": "https://github.com/dnsquery/dns-packet",
"main": "./index.js",
"module": "./index.mjs",
"types": "./types/index.d.ts",
"exports": {
".": {
"types": "./types/index.d.ts",
"import": "./index.mjs",
"require": "./index.js"
},
"./buffer_utils.js": {
"types": "./buffer_utils.mjs",
"import": "./buffer_utils.mjs",
"require": "./buffer_utils.js"
},
"./classes.js": {
"types": "./classes.d.ts",
"import": "./classes.mjs",
"require": "./classes.js"
},
"./opcodes.js": {
"types": "./opcodes.d.ts",
"import": "./opcodes.mjs",
"require": "./opcodes.js"
},
"./optioncodes.js": {
"types": "./optioncodes.d.ts",
"import": "./optioncodes.mjs",
"require": "./optioncodes.js"
},
"./rcodes.js": {
"types": "./rcodes.d.ts",
"import": "./rcodes.mjs",
"require": "./rcodes.js"
},
"./types.js": {
"types": "./types.d.ts",
"import": "./types.mjs",
"require": "./types.js"
}
},
"engines": {
"node": ">=6"
},
"scripts": {
"clean": "rm -rf coverage",
"lint": "standard && dtslint types --localTs node_modules/typescript/lib",
"test": "npm run lint && npm run unit",
"unit": "tape test.mjs",
"coverage": "c8 -r html npm test",
"prepare": "npx @leichtgewicht/esm2umd"
},
"dependencies": {
"@leichtgewicht/ip-codec": "^2.0.4",
"utf8-codec": "^1.0.0"
},
"devDependencies": {
"@definitelytyped/dtslint": "^0.0.119",
"@leichtgewicht/esm2umd": "^0.4.0",
"c8": "^7.12.0",
"standard": "^17.0.0",
"tape": "^5.6.1",
"typescript": "^4.9.3"
},
"keywords": [
"dns",
"packet",
"encodings",
"encoding",
"encoder",
"abstract-encoding"
]
}
+3
View File
@@ -0,0 +1,3 @@
export type RecordClass = "IN" | "CS" | "CH" | "HS" | "ANY" | string;
export function toString (type: number): RecordClass;
export function toRcode (name: RecordClass): number;
+86
View File
@@ -0,0 +1,86 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.toRcode = toRcode;
exports.toString = toString;
/*
* Traditional DNS header RCODEs (4-bits) defined by IANA in
* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
*/
function toString(rcode) {
switch (rcode) {
case 0:
return 'NOERROR';
case 1:
return 'FORMERR';
case 2:
return 'SERVFAIL';
case 3:
return 'NXDOMAIN';
case 4:
return 'NOTIMP';
case 5:
return 'REFUSED';
case 6:
return 'YXDOMAIN';
case 7:
return 'YXRRSET';
case 8:
return 'NXRRSET';
case 9:
return 'NOTAUTH';
case 10:
return 'NOTZONE';
case 11:
return 'RCODE_11';
case 12:
return 'RCODE_12';
case 13:
return 'RCODE_13';
case 14:
return 'RCODE_14';
case 15:
return 'RCODE_15';
}
return 'RCODE_' + rcode;
}
function toRcode(code) {
switch (code.toUpperCase()) {
case 'NOERROR':
return 0;
case 'FORMERR':
return 1;
case 'SERVFAIL':
return 2;
case 'NXDOMAIN':
return 3;
case 'NOTIMP':
return 4;
case 'REFUSED':
return 5;
case 'YXDOMAIN':
return 6;
case 'YXRRSET':
return 7;
case 'NXRRSET':
return 8;
case 'NOTAUTH':
return 9;
case 'NOTZONE':
return 10;
case 'RCODE_11':
return 11;
case 'RCODE_12':
return 12;
case 'RCODE_13':
return 13;
case 'RCODE_14':
return 14;
case 'RCODE_15':
return 15;
}
return 0;
}
+48
View File
@@ -0,0 +1,48 @@
/*
* Traditional DNS header RCODEs (4-bits) defined by IANA in
* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml
*/
export function toString (rcode) {
switch (rcode) {
case 0: return 'NOERROR'
case 1: return 'FORMERR'
case 2: return 'SERVFAIL'
case 3: return 'NXDOMAIN'
case 4: return 'NOTIMP'
case 5: return 'REFUSED'
case 6: return 'YXDOMAIN'
case 7: return 'YXRRSET'
case 8: return 'NXRRSET'
case 9: return 'NOTAUTH'
case 10: return 'NOTZONE'
case 11: return 'RCODE_11'
case 12: return 'RCODE_12'
case 13: return 'RCODE_13'
case 14: return 'RCODE_14'
case 15: return 'RCODE_15'
}
return 'RCODE_' + rcode
}
export function toRcode (code) {
switch (code.toUpperCase()) {
case 'NOERROR': return 0
case 'FORMERR': return 1
case 'SERVFAIL': return 2
case 'NXDOMAIN': return 3
case 'NOTIMP': return 4
case 'REFUSED': return 5
case 'YXDOMAIN': return 6
case 'YXRRSET': return 7
case 'NXRRSET': return 8
case 'NOTAUTH': return 9
case 'NOTZONE': return 10
case 'RCODE_11': return 11
case 'RCODE_12': return 12
case 'RCODE_13': return 13
case 'RCODE_14': return 14
case 'RCODE_15': return 15
}
return 0
}
+750
View File
@@ -0,0 +1,750 @@
"use strict";
var _tape = _interopRequireWildcard(require("tape"), true);
var packet = _interopRequireWildcard(require("./index.js"), true);
var rcodes = _interopRequireWildcard(require("./rcodes.js"), true);
var opcodes = _interopRequireWildcard(require("./opcodes.js"), true);
var optioncodes = _interopRequireWildcard(require("./optioncodes.js"), true);
var _utf8Codec = require("utf8-codec");
var _buffer_utils = require("./buffer_utils.js");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
(0, _tape.default)('unknown', function (t) {
testEncoder(t, packet.unknown, Buffer.from('hello world'));
t.end();
});
(0, _tape.default)('txt', function (t) {
testEncoder(t, packet.txt, []);
testEncoder(t, packet.txt, ['hello world']);
testEncoder(t, packet.txt, ['hello', 'world']);
testEncoder(t, packet.txt, [Buffer.from([0, 1, 2, 3, 4, 5])]);
testEncoder(t, packet.txt, ['a', 'b', Buffer.from([0, 1, 2, 3, 4, 5])]);
testEncoder(t, packet.txt, ['', Buffer.allocUnsafe(0)]);
t.end();
});
(0, _tape.default)('txt-scalar-string', function (t) {
const buf = packet.txt.encode('hi');
const val = packet.txt.decode(buf);
t.equal(val.length, 1, 'array length');
t.ok(compare(t, val[0], Buffer.from('hi')), 'streamDecoded type match');
t.end();
});
(0, _tape.default)('txt-scalar-buffer', function (t) {
const data = Buffer.from([0, 1, 2, 3, 4, 5]);
const buf = packet.txt.encode(data);
const val = packet.txt.decode(buf);
t.equal(val.length, 1, 'array length');
t.ok(compare(t, val[0], data), 'data');
t.end();
});
(0, _tape.default)('txt-invalid-data', function (t) {
t.throws(function () {
packet.txt.encode(null);
}, 'null');
t.throws(function () {
packet.txt.encode(undefined);
}, 'undefined');
t.throws(function () {
packet.txt.encode(10);
}, 'number');
t.end();
});
(0, _tape.default)('null', function (t) {
testEncoder(t, packet.null, Buffer.from([0, 1, 2, 3, 4, 5]));
t.end();
});
(0, _tape.default)('hinfo', function (t) {
testEncoder(t, packet.hinfo, {
cpu: 'intel',
os: 'best one'
});
t.end();
});
(0, _tape.default)('ptr', function (t) {
testEncoder(t, packet.ptr, 'hello.world.com');
t.end();
});
(0, _tape.default)('cname', function (t) {
testEncoder(t, packet.cname, 'hello.cname.world.com');
t.end();
});
(0, _tape.default)('dname', function (t) {
testEncoder(t, packet.dname, 'hello.dname.world.com');
t.end();
});
(0, _tape.default)('srv', function (t) {
testEncoder(t, packet.srv, {
port: 9999,
target: 'hello.world.com'
});
testEncoder(t, packet.srv, {
port: 9999,
target: 'hello.world.com',
priority: 42,
weight: 10
});
t.end();
});
(0, _tape.default)('caa', function (t) {
testEncoder(t, packet.caa, {
flags: 128,
tag: 'issue',
value: 'letsencrypt.org',
issuerCritical: true
});
testEncoder(t, packet.caa, {
tag: 'issue',
value: 'letsencrypt.org',
issuerCritical: true
});
testEncoder(t, packet.caa, {
tag: 'issue',
value: 'letsencrypt.org'
});
t.end();
});
(0, _tape.default)('mx', function (t) {
testEncoder(t, packet.mx, {
preference: 10,
exchange: 'mx.hello.world.com'
});
testEncoder(t, packet.mx, {
exchange: 'mx.hello.world.com'
});
t.end();
});
(0, _tape.default)('ns', function (t) {
testEncoder(t, packet.ns, 'ns.world.com');
t.end();
});
(0, _tape.default)('soa', function (t) {
testEncoder(t, packet.soa, {
mname: 'hello.world.com',
rname: 'root.hello.world.com',
serial: 2018010400,
refresh: 14400,
retry: 3600,
expire: 604800,
minimum: 3600
});
t.end();
});
(0, _tape.default)('sshfp', function (t) {
testEncoder(t, packet.sshfp, {
algorithm: 1,
hash: 1,
fingerprint: 'a108c9f834354d5b37af988141c9294822f5bc00'
});
testEncoder(t, packet.sshfp, {
algorithm: 1,
hash: 2,
fingerprint: 'a108c9f834354d5b37af988141c9294822f5bc00afa0dfafa2dfa1dfafa5dfa1'
});
t.end();
});
(0, _tape.default)('a', function (t) {
testEncoder(t, packet.a, '127.0.0.1');
t.end();
});
(0, _tape.default)('aaaa', function (t) {
testEncoder(t, packet.aaaa, 'fe80::1');
t.end();
});
(0, _tape.default)('query', function (t) {
testEncoder(t, packet, {
type: 'query',
questions: [{
type: 'A',
name: 'hello.a.com'
}, {
type: 'SRV',
name: 'hello.srv.com'
}]
});
testEncoder(t, packet, {
type: 'query',
id: 42,
questions: [{
type: 'A',
class: 'IN',
name: 'hello.a.com'
}, {
type: 'SRV',
name: 'hello.srv.com'
}]
});
testEncoder(t, packet, {
type: 'query',
id: 42,
questions: [{
type: 'A',
class: 'CH',
name: 'hello.a.com'
}, {
type: 'SRV',
name: 'hello.srv.com'
}]
});
t.end();
});
(0, _tape.default)('response', function (t) {
testEncoder(t, packet, {
type: 'response',
answers: [{
type: 'A',
class: 'IN',
flush: true,
name: 'hello.a.com',
data: '127.0.0.1'
}]
});
testEncoder(t, packet, {
type: 'response',
flags: packet.TRUNCATED_RESPONSE,
answers: [{
type: 'A',
class: 'IN',
name: 'hello.a.com',
data: '127.0.0.1'
}, {
type: 'SRV',
class: 'IN',
name: 'hello.srv.com',
data: {
port: 9090,
target: 'hello.target.com'
}
}, {
type: 'CNAME',
class: 'IN',
name: 'hello.cname.com',
data: 'hello.other.domain.com'
}]
});
testEncoder(t, packet, {
type: 'response',
id: 100,
flags: 0,
additionals: [{
type: 'AAAA',
name: 'hello.a.com',
data: 'fe80::1'
}, {
type: 'PTR',
name: 'hello.ptr.com',
data: 'hello.other.ptr.com'
}, {
type: 'SRV',
name: 'hello.srv.com',
ttl: 42,
data: {
port: 9090,
target: 'hello.target.com'
}
}],
answers: [{
type: 'NULL',
name: 'hello.null.com',
data: Buffer.from([1, 2, 3, 4, 5])
}]
});
testEncoder(t, packet, {
type: 'response',
answers: [{
type: 'TXT',
name: 'emptytxt.com',
data: ''
}]
});
t.end();
});
(0, _tape.default)('rcode', function (t) {
const errors = ['NOERROR', 'FORMERR', 'SERVFAIL', 'NXDOMAIN', 'NOTIMP', 'REFUSED', 'YXDOMAIN', 'YXRRSET', 'NXRRSET', 'NOTAUTH', 'NOTZONE', 'RCODE_11', 'RCODE_12', 'RCODE_13', 'RCODE_14', 'RCODE_15'];
for (const i in errors) {
const code = rcodes.toRcode(errors[i]);
t.ok(errors[i] === rcodes.toString(code), 'rcode conversion from/to string matches: ' + rcodes.toString(code));
}
const ops = ['QUERY', 'IQUERY', 'STATUS', 'OPCODE_3', 'NOTIFY', 'UPDATE', 'OPCODE_6', 'OPCODE_7', 'OPCODE_8', 'OPCODE_9', 'OPCODE_10', 'OPCODE_11', 'OPCODE_12', 'OPCODE_13', 'OPCODE_14', 'OPCODE_15'];
for (const j in ops) {
const ocode = opcodes.toOpcode(ops[j]);
t.ok(ops[j] === opcodes.toString(ocode), 'opcode conversion from/to string matches: ' + opcodes.toString(ocode));
}
const buf = packet.encode({
type: 'response',
id: 45632,
flags: 0x8480,
answers: [{
type: 'A',
name: 'hello.example.net',
data: '127.0.0.1'
}]
});
const val = packet.decode(buf);
t.ok(val.type === 'response', 'decode type');
t.ok(val.opcode === 'QUERY', 'decode opcode');
t.ok(val.flag_qr === true, 'decode flag_qr');
t.ok(val.flag_aa === true, 'decode flag_aa');
t.ok(val.flag_tc === false, 'decode flag_tc');
t.ok(val.flag_rd === false, 'decode flag_rd');
t.ok(val.flag_ra === true, 'decode flag_ra');
t.ok(val.flag_z === false, 'decode flag_z');
t.ok(val.flag_ad === false, 'decode flag_ad');
t.ok(val.flag_cd === false, 'decode flag_cd');
t.ok(val.rcode === 'NOERROR', 'decode rcode');
t.end();
});
(0, _tape.default)('name_encoding', function (t) {
let data = 'foo.example.com';
const buf = Buffer.allocUnsafe(255);
let offset = 0;
packet.name.encode(data, buf, offset);
t.ok(packet.name.encode.bytes === 17, 'name encoding length matches');
let dd = packet.name.decode(buf, offset);
t.ok(data === dd, 'encode/decode matches');
offset += packet.name.encode.bytes;
data = 'com';
packet.name.encode(data, buf, offset);
t.ok(packet.name.encode.bytes === 5, 'name encoding length matches');
dd = packet.name.decode(buf, offset);
t.ok(data === dd, 'encode/decode matches');
offset += packet.name.encode.bytes;
data = 'example.com.';
packet.name.encode(data, buf, offset);
t.ok(packet.name.encode.bytes === 13, 'name encoding length matches');
dd = packet.name.decode(buf, offset);
t.ok(data.slice(0, -1) === dd, 'encode/decode matches');
offset += packet.name.encode.bytes;
data = '.';
packet.name.encode(data, buf, offset);
t.ok(packet.name.encode.bytes === 1, 'name encoding length matches');
dd = packet.name.decode(buf, offset);
t.ok(data === dd, 'encode/decode matches');
t.end();
});
(0, _tape.default)('name_decoding', function (t) {
// The two most significant bits of a valid label header must be either both zero or both one
t.throws(function () {
packet.name.decode(Buffer.from([0x80]));
}, /Cannot decode name \(bad label\)$/);
t.throws(function () {
packet.name.decode(Buffer.from([0xb0]));
}, /Cannot decode name \(bad label\)$/);
// Ensure there's enough buffer to read
t.throws(function () {
packet.name.decode(Buffer.from([]));
}, /Cannot decode name \(buffer overflow\)$/);
t.throws(function () {
packet.name.decode(Buffer.from([0x01, 0x00]));
}, /Cannot decode name \(buffer overflow\)$/);
t.throws(function () {
packet.name.decode(Buffer.from([0x01]));
}, /Cannot decode name \(buffer overflow\)$/);
t.throws(function () {
packet.name.decode(Buffer.from([0xc0]));
}, /Cannot decode name \(buffer overflow\)$/);
// Allow only pointers backwards
t.throws(function () {
packet.name.decode(Buffer.from([0xc0, 0x00]));
}, /Cannot decode name \(bad pointer\)$/);
t.throws(function () {
packet.name.decode(Buffer.from([0xc0, 0x01]));
}, /Cannot decode name \(bad pointer\)$/);
// A name can be only 253 characters (when connected with dots)
const maxLength = Buffer.alloc(255);
maxLength.fill(Buffer.from([0x01, 0x61]), 0, 254);
t.ok(packet.name.decode(maxLength) === new Array(127).fill('a').join('.'));
const tooLong = Buffer.alloc(256);
tooLong.fill(Buffer.from([0x01, 0x61]));
t.throws(function () {
packet.name.decode(tooLong);
}, /Cannot decode name \(name too long\)$/);
// Ensure jumps don't reset the total length counter
const tooLongWithJump = Buffer.alloc(403);
tooLongWithJump.fill(Buffer.from([0x01, 0x61]), 0, 200);
tooLongWithJump.fill(Buffer.from([0x01, 0x61]), 201, 401);
tooLongWithJump.set([0xc0, 0x00], 401);
t.throws(function () {
packet.name.decode(tooLongWithJump, 201);
}, /Cannot decode name \(name too long\)$/);
// Ensure a jump to a null byte doesn't add extra dots
t.ok(packet.name.decode(Buffer.from([0x00, 0x01, 0x61, 0xc0, 0x00]), 1) === 'a');
// Ensure deeply nested pointers don't cause "Maximum call stack size exceeded" errors
const buf = Buffer.alloc(16386);
for (let i = 0; i < 16384; i += 2) {
buf.writeUInt16BE(0xc000 | i, i + 2);
}
t.ok(packet.name.decode(buf, 16384) === '.');
t.end();
});
(0, _tape.default)('stream', function (t) {
const val = {
type: 'query',
id: 45632,
flags: 0x8480,
answers: [{
type: 'A',
name: 'test2.example.net',
data: '198.51.100.1'
}]
};
const buf = packet.streamEncode(val);
const val2 = packet.streamDecode(buf);
t.same(buf.length, packet.streamEncode.bytes, 'streamEncode.bytes was set correctly');
t.ok(compare(t, val2.type, val.type), 'streamDecoded type match');
t.ok(compare(t, val2.id, val.id), 'streamDecoded id match');
t.ok(parseInt(val2.flags) === parseInt(val.flags & 0x7FFF), 'streamDecoded flags match');
const answer = val.answers[0];
const answer2 = val2.answers[0];
t.ok(compare(t, answer.type, answer2.type), 'streamDecoded RR type match');
t.ok(compare(t, answer.name, answer2.name), 'streamDecoded RR name match');
t.ok(compare(t, answer.data, answer2.data), 'streamDecoded RR rdata match');
t.end();
});
(0, _tape.default)('opt', function (t) {
const val = {
type: 'query',
questions: [{
type: 'A',
name: 'hello.a.com'
}],
additionals: [{
type: 'OPT',
name: '.',
udpPayloadSize: 1024
}]
};
testEncoder(t, packet, val);
let buf = packet.encode(val);
let val2 = packet.decode(buf);
const additional1 = val.additionals[0];
let additional2 = val2.additionals[0];
t.ok(compare(t, additional1.name, additional2.name), 'name matches');
t.ok(compare(t, additional1.udpPayloadSize, additional2.udpPayloadSize), 'udp payload size matches');
t.ok(compare(t, 0, additional2.flags), 'flags match');
additional1.flags = packet.DNSSEC_OK;
additional1.extendedRcode = 0x80;
additional1.options = [{
code: 'CLIENT_SUBNET',
// edns-client-subnet, see RFC 7871
ip: 'fe80::',
sourcePrefixLength: 64
}, {
code: 8,
// still ECS
ip: '5.6.0.0',
sourcePrefixLength: 16,
scopePrefixLength: 16
}, {
code: 'padding',
length: 31
}, {
code: 'TCP_KEEPALIVE'
}, {
code: 'tcp_keepalive',
timeout: 150
}, {
code: 'KEY_TAG',
tags: [1, 82, 987]
}];
buf = packet.encode(val);
val2 = packet.decode(buf);
additional2 = val2.additionals[0];
t.ok(compare(t, 1 << 15, additional2.flags), 'DO bit set in flags');
t.ok(compare(t, true, additional2.flag_do), 'DO bit set');
t.ok(compare(t, additional1.extendedRcode, additional2.extendedRcode), 'extended rcode matches');
t.ok(compare(t, 8, additional2.options[0].code));
t.ok(compare(t, 'fe80::', additional2.options[0].ip));
t.ok(compare(t, 64, additional2.options[0].sourcePrefixLength));
t.ok(compare(t, '5.6.0.0', additional2.options[1].ip));
t.ok(compare(t, 16, additional2.options[1].sourcePrefixLength));
t.ok(compare(t, 16, additional2.options[1].scopePrefixLength));
t.ok(compare(t, additional1.options[2].length, additional2.options[2].data.length));
t.ok(compare(t, additional1.options[3].timeout, undefined));
t.ok(compare(t, additional1.options[4].timeout, additional2.options[4].timeout));
t.ok(compare(t, additional1.options[5].tags, additional2.options[5].tags));
t.end();
});
(0, _tape.default)('dnskey', function (t) {
testEncoder(t, packet.dnskey, {
flags: packet.dnskey.SECURE_ENTRYPOINT | packet.dnskey.ZONE_KEY,
algorithm: 1,
key: Buffer.from([0, 1, 2, 3, 4, 5])
});
t.end();
});
(0, _tape.default)('rrsig', function (t) {
const testRRSIG = {
typeCovered: 'A',
algorithm: 1,
labels: 2,
originalTTL: 3600,
expiration: 1234,
inception: 1233,
keyTag: 2345,
signersName: 'foo.com',
signature: Buffer.from([0, 1, 2, 3, 4, 5])
};
testEncoder(t, packet.rrsig, testRRSIG);
// Check the signature length is correct with extra junk at the end
const buf = Buffer.allocUnsafe(packet.rrsig.encodingLength(testRRSIG) + 4);
packet.rrsig.encode(testRRSIG, buf);
const val2 = packet.rrsig.decode(buf);
t.ok(compare(t, testRRSIG, val2));
t.end();
});
(0, _tape.default)('rrp', function (t) {
testEncoder(t, packet.rp, {
mbox: 'foo.bar.com',
txt: 'baz.bar.com'
});
testEncoder(t, packet.rp, {
mbox: 'foo.bar.com'
});
testEncoder(t, packet.rp, {
txt: 'baz.bar.com'
});
testEncoder(t, packet.rp, {});
t.end();
});
(0, _tape.default)('nsec', function (t) {
testEncoder(t, packet.nsec, {
nextDomain: 'foo.com',
rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV']
});
testEncoder(t, packet.nsec, {
nextDomain: 'foo.com',
rrtypes: ['TXT'] // 16
});
testEncoder(t, packet.nsec, {
nextDomain: 'foo.com',
rrtypes: ['TKEY'] // 249
});
testEncoder(t, packet.nsec, {
nextDomain: 'foo.com',
rrtypes: ['RRSIG', 'NSEC']
});
testEncoder(t, packet.nsec, {
nextDomain: 'foo.com',
rrtypes: ['TXT', 'RRSIG']
});
testEncoder(t, packet.nsec, {
nextDomain: 'foo.com',
rrtypes: ['TXT', 'NSEC']
});
// Test with the sample NSEC from https://tools.ietf.org/html/rfc4034#section-4.3
const sampleNSEC = new Uint8Array(Buffer.from('003704686f7374076578616d706c6503636f6d00' + '0006400100000003041b000000000000000000000000000000000000000000000' + '000000020', 'hex'));
const decoded = packet.nsec.decode(sampleNSEC);
t.ok(compare(t, decoded, {
nextDomain: 'host.example.com',
rrtypes: ['A', 'MX', 'RRSIG', 'NSEC', 'UNKNOWN_1234']
}));
const reencoded = packet.nsec.encode(decoded);
t.same(sampleNSEC.length, reencoded.length);
t.same(sampleNSEC, reencoded);
t.end();
});
(0, _tape.default)('nsec3', function (t) {
testEncoder(t, packet.nsec3, {
algorithm: 1,
flags: 0,
iterations: 257,
salt: Buffer.from([42, 42, 42]),
nextDomain: Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV']
});
t.end();
});
(0, _tape.default)('ds', function (t) {
testEncoder(t, packet.ds, {
keyTag: 1234,
algorithm: 1,
digestType: 1,
digest: Buffer.from([0, 1, 2, 3, 4, 5])
});
t.end();
});
(0, _tape.default)('unpack', function (t) {
const buf = Buffer.from([0x00, 0x79, 0xde, 0xad, 0x85, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x02, 0x6f, 0x6a, 0x05, 0x62, 0x61, 0x6e, 0x67, 0x6a, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x04, 0x81, 0xfa, 0x0b, 0xaa, 0xc0, 0x0f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x05, 0x02, 0x63, 0x6a, 0xc0, 0x0f, 0xc0, 0x0f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x02, 0xc0, 0x0c, 0xc0, 0x3a, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x04, 0x45, 0x4d, 0x9b, 0x9c, 0xc0, 0x0c, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x10, 0x20, 0x01, 0x04, 0x18, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf9]);
const val = packet.streamDecode(buf);
const answer = val.answers[0];
const authority = val.authorities[1];
t.ok(val.rcode === 'NOERROR', 'decode rcode');
t.ok(compare(t, answer.type, 'A'), 'streamDecoded RR type match');
t.ok(compare(t, answer.name, 'oj.bangj.com'), 'streamDecoded RR name match');
t.ok(compare(t, answer.data, '129.250.11.170'), 'streamDecoded RR rdata match');
t.ok(compare(t, authority.type, 'NS'), 'streamDecoded RR type match');
t.ok(compare(t, authority.name, 'bangj.com'), 'streamDecoded RR name match');
t.ok(compare(t, authority.data, 'oj.bangj.com'), 'streamDecoded RR rdata match');
t.end();
});
(0, _tape.default)('optioncodes', function (t) {
const opts = [[0, 'OPTION_0'], [1, 'LLQ'], [2, 'UL'], [3, 'NSID'], [4, 'OPTION_4'], [5, 'DAU'], [6, 'DHU'], [7, 'N3U'], [8, 'CLIENT_SUBNET'], [9, 'EXPIRE'], [10, 'COOKIE'], [11, 'TCP_KEEPALIVE'], [12, 'PADDING'], [13, 'CHAIN'], [14, 'KEY_TAG'], [26946, 'DEVICEID'], [65535, 'OPTION_65535'], [64000, 'OPTION_64000'], [65002, 'OPTION_65002'], [-1, null]];
for (const [code, str] of opts) {
const s = optioncodes.toString(code);
t.ok(compare(t, s, str), `${code} => ${str}`);
t.ok(compare(t, optioncodes.toCode(s), code), `${str} => ${code}`);
}
t.ok(compare(t, optioncodes.toCode('INVALIDINVALID'), -1));
t.end();
});
(0, _tape.default)('packet exported codec', function (t) {
const input = {
type: 'query',
questions: [{
type: 'A',
name: 'hello.a.com',
class: 'IN'
}]
};
packet.encode.bytes = 0;
t.equals(packet.packet.encode.bytes, 0);
t.equals(packet.packet.encode.bytes, packet.encode.bytes);
const buf = packet.packet.encode(input);
t.equals(packet.packet.encode.bytes, 29);
t.equals(packet.packet.encode.bytes, packet.encode.bytes);
t.deepEqual(buf, packet.encode(input));
packet.decode.bytes = 0;
t.equals(packet.packet.decode.bytes, 0);
t.equals(packet.packet.decode.bytes, packet.decode.bytes);
const obj = packet.packet.decode(buf);
t.equals(packet.packet.decode.bytes, 29);
t.equals(packet.packet.decode.bytes, packet.decode.bytes);
t.deepEqual(obj, packet.decode(buf));
t.end();
});
(0, _tape.default)('single query error with multiple questions', function (t) {
t.throws(() => {
packet.query.encode({
questions: []
});
}, /Only one .question object expected instead of a .questions array!/);
t.end();
});
(0, _tape.default)('single query -> response encoding', function (t) {
const question = {
type: 'A',
name: 'hello.a.com',
class: 'IN'
};
const length = packet.query.encodingLength({
question
});
t.equals(length, 29);
t.equals(packet.query.encode.bytes, 0);
const queryBytes = packet.query.encode({
question
});
const decodedQuestion = packet.decode(queryBytes);
t.equals(packet.query.encode.bytes, length);
t.equal(decodedQuestion.type, 'query');
t.deepEqual(decodedQuestion.questions, [question]);
t.equals(packet.query.decode.bytes, 0);
decodedQuestion.question = decodedQuestion.questions[0];
delete decodedQuestion.questions;
t.deepEqual(packet.query.decode(queryBytes), decodedQuestion);
const responseBytes = packet.encode({
type: 'response',
questions: [question]
});
const decodedResponse = packet.response.decode(responseBytes);
t.equal(packet.response.encodingLength(decodedResponse), length);
t.equals(packet.response.decode.bytes, length);
t.deepEqual(decodedResponse.question, question);
t.deepEqual(packet.response.encode(decodedResponse), responseBytes);
t.end();
});
(0, _tape.test)('buffer utf8', function (sub) {
for (const [index, fixture] of [
// '', // empty
'basic: hi', 'japanese: 日本語', 'mixed: 日本語 hi', 'min 1 byte: \x00', 'odd 1 byte: \x4c', 'max 1 byte: \x7f', 'min 2 byte: \x80', 'odd 2 byte: \xd941', 'max 2 byte: \x7fff', 'min 3 byte: \x8000', 'odd 3 byte: \xa158', 'max 3 byte: \xffff', `4 byte: ${String.fromCodePoint(100000)}`].entries()) {
sub.test(`fixture #${index}`, t => {
const check = Buffer.from(fixture);
const checkHex = check.toString('hex');
const len = check.length;
t.equals((0, _buffer_utils.bytelength)(fixture), len, `fixture ${fixture} length`);
const buf = new Uint8Array(len);
t.equals((0, _buffer_utils.write)(buf, fixture, 0), len, 'write.num');
t.equals((0, _buffer_utils.toHex)(buf, 0, len), checkHex, `write: ${fixture}`);
t.equals(check.compare(Buffer.from((0, _buffer_utils.writeHex)(buf, checkHex, 0, (0, _buffer_utils.hexLength)(checkHex)))), 0);
t.equals((0, _utf8Codec.decode)(check, 0, check.length), check.toString(), `toUtf8: ${fixture}`);
t.end();
});
}
sub.test('surrogate pairs', function (t) {
[[0xd821, 0xdea0], [0xd800, 0xdc00], [0xd801, 0xdc01], [0xddff, 0xdfff], [0xd821, 0x0000], [0xd821, 0xd821, 0xdea0]].forEach(function (bytes, index) {
const str = String.fromCharCode(...bytes);
const buf = new Uint8Array((0, _buffer_utils.bytelength)(str));
const check = Buffer.from(str);
t.equal(buf.length, check.length);
(0, _buffer_utils.write)(buf, str, 0);
t.equal((0, _buffer_utils.toHex)(buf, 0, buf.length), check.toString('hex'), `#${index} [${bytes}].toHex() ... ${check.toString('hex')}`);
t.equal((0, _utf8Codec.decode)(buf, 0, buf.length), check.toString(), `#${index} [${bytes}].toUtf8`);
});
t.end();
});
sub.test('all code points', function (t) {
const blockSize = 2048;
const blocks = 65536 / blockSize;
let code = 0;
for (let block = 0; block < blocks; block += 1) {
const expected = {};
const actual = {};
for (let i = 0; i < blockSize; i += 1, code += 1) {
const str = String.fromCharCode(code);
const buf = new Uint8Array((0, _buffer_utils.bytelength)(str));
(0, _buffer_utils.write)(buf, str, 0);
const exp = Buffer.from(str);
expected[code] = exp.toString('hex');
actual[code] = (0, _buffer_utils.toHex)(buf, 0, buf.length);
}
t.same(actual, expected);
}
t.end();
});
});
function testEncoder(t, rpacket, val) {
const buf = rpacket.encode(val);
const val2 = rpacket.decode(buf);
t.same(buf.length, rpacket.encode.bytes, 'encode.bytes was set correctly');
t.same(buf.length, rpacket.encodingLength(val), 'encoding length matches');
t.ok(compare(t, val, val2), 'decoded object match');
const buf2 = rpacket.encode(val2);
const val3 = rpacket.decode(buf2);
t.same(buf2.length, rpacket.encode.bytes, 'encode.bytes was set correctly on re-encode');
t.same(buf2.length, rpacket.encodingLength(val), 'encoding length matches on re-encode');
t.ok(compare(t, val, val3), 'decoded object match on re-encode');
t.ok(compare(t, val2, val3), 're-encoded decoded object match on re-encode');
const bigger = Buffer.allocUnsafe(buf2.length + 10);
const buf3 = rpacket.encode(val, bigger, 10);
const val4 = rpacket.decode(buf3, 10);
t.ok(buf3 === bigger, 'echoes buffer on external buffer');
t.same(rpacket.encode.bytes, buf.length, 'encode.bytes is the same on external buffer');
t.ok(compare(t, val, val4), 'decoded object match on external buffer');
}
function compare(t, a, b) {
if (a instanceof Uint8Array) return (0, _buffer_utils.toHex)(a, 0, a.length) === (0, _buffer_utils.toHex)(b, 0, b.length);
if (typeof a === 'object' && a && b) {
const keys = Object.keys(a);
for (let i = 0; i < keys.length; i++) {
if (!compare(t, a[keys[i]], b[keys[i]])) {
return false;
}
}
} else if (Array.isArray(b) && !Array.isArray(a)) {
// TXT always decode as array
return a.toString() === b[0].toString();
} else {
return a === b;
}
return true;
}
+817
View File
@@ -0,0 +1,817 @@
import tape, { test } from 'tape'
import * as packet from './index.mjs'
import * as rcodes from './rcodes.mjs'
import * as opcodes from './opcodes.mjs'
import * as optioncodes from './optioncodes.mjs'
import { decode as toUtf8 } from 'utf8-codec'
import { write, toHex, bytelength, writeHex, hexLength } from './buffer_utils.mjs'
tape('unknown', function (t) {
testEncoder(t, packet.unknown, Buffer.from('hello world'))
t.end()
})
tape('txt', function (t) {
testEncoder(t, packet.txt, [])
testEncoder(t, packet.txt, ['hello world'])
testEncoder(t, packet.txt, ['hello', 'world'])
testEncoder(t, packet.txt, [Buffer.from([0, 1, 2, 3, 4, 5])])
testEncoder(t, packet.txt, ['a', 'b', Buffer.from([0, 1, 2, 3, 4, 5])])
testEncoder(t, packet.txt, ['', Buffer.allocUnsafe(0)])
t.end()
})
tape('txt-scalar-string', function (t) {
const buf = packet.txt.encode('hi')
const val = packet.txt.decode(buf)
t.equal(val.length, 1, 'array length')
t.ok(compare(t, val[0], Buffer.from('hi')), 'streamDecoded type match')
t.end()
})
tape('txt-scalar-buffer', function (t) {
const data = Buffer.from([0, 1, 2, 3, 4, 5])
const buf = packet.txt.encode(data)
const val = packet.txt.decode(buf)
t.equal(val.length, 1, 'array length')
t.ok(compare(t, val[0], data), 'data')
t.end()
})
tape('txt-invalid-data', function (t) {
t.throws(function () { packet.txt.encode(null) }, 'null')
t.throws(function () { packet.txt.encode(undefined) }, 'undefined')
t.throws(function () { packet.txt.encode(10) }, 'number')
t.end()
})
tape('null', function (t) {
testEncoder(t, packet.null, Buffer.from([0, 1, 2, 3, 4, 5]))
t.end()
})
tape('hinfo', function (t) {
testEncoder(t, packet.hinfo, { cpu: 'intel', os: 'best one' })
t.end()
})
tape('ptr', function (t) {
testEncoder(t, packet.ptr, 'hello.world.com')
t.end()
})
tape('cname', function (t) {
testEncoder(t, packet.cname, 'hello.cname.world.com')
t.end()
})
tape('dname', function (t) {
testEncoder(t, packet.dname, 'hello.dname.world.com')
t.end()
})
tape('srv', function (t) {
testEncoder(t, packet.srv, { port: 9999, target: 'hello.world.com' })
testEncoder(t, packet.srv, { port: 9999, target: 'hello.world.com', priority: 42, weight: 10 })
t.end()
})
tape('caa', function (t) {
testEncoder(t, packet.caa, { flags: 128, tag: 'issue', value: 'letsencrypt.org', issuerCritical: true })
testEncoder(t, packet.caa, { tag: 'issue', value: 'letsencrypt.org', issuerCritical: true })
testEncoder(t, packet.caa, { tag: 'issue', value: 'letsencrypt.org' })
t.end()
})
tape('mx', function (t) {
testEncoder(t, packet.mx, { preference: 10, exchange: 'mx.hello.world.com' })
testEncoder(t, packet.mx, { exchange: 'mx.hello.world.com' })
t.end()
})
tape('ns', function (t) {
testEncoder(t, packet.ns, 'ns.world.com')
t.end()
})
tape('soa', function (t) {
testEncoder(t, packet.soa, {
mname: 'hello.world.com',
rname: 'root.hello.world.com',
serial: 2018010400,
refresh: 14400,
retry: 3600,
expire: 604800,
minimum: 3600
})
t.end()
})
tape('sshfp', function (t) {
testEncoder(t, packet.sshfp, {
algorithm: 1,
hash: 1,
fingerprint: 'a108c9f834354d5b37af988141c9294822f5bc00'
})
testEncoder(t, packet.sshfp, {
algorithm: 1,
hash: 2,
fingerprint: 'a108c9f834354d5b37af988141c9294822f5bc00afa0dfafa2dfa1dfafa5dfa1'
})
t.end()
})
tape('a', function (t) {
testEncoder(t, packet.a, '127.0.0.1')
t.end()
})
tape('aaaa', function (t) {
testEncoder(t, packet.aaaa, 'fe80::1')
t.end()
})
tape('query', function (t) {
testEncoder(t, packet, {
type: 'query',
questions: [{
type: 'A',
name: 'hello.a.com'
}, {
type: 'SRV',
name: 'hello.srv.com'
}]
})
testEncoder(t, packet, {
type: 'query',
id: 42,
questions: [{
type: 'A',
class: 'IN',
name: 'hello.a.com'
}, {
type: 'SRV',
name: 'hello.srv.com'
}]
})
testEncoder(t, packet, {
type: 'query',
id: 42,
questions: [{
type: 'A',
class: 'CH',
name: 'hello.a.com'
}, {
type: 'SRV',
name: 'hello.srv.com'
}]
})
t.end()
})
tape('response', function (t) {
testEncoder(t, packet, {
type: 'response',
answers: [{
type: 'A',
class: 'IN',
flush: true,
name: 'hello.a.com',
data: '127.0.0.1'
}]
})
testEncoder(t, packet, {
type: 'response',
flags: packet.TRUNCATED_RESPONSE,
answers: [{
type: 'A',
class: 'IN',
name: 'hello.a.com',
data: '127.0.0.1'
}, {
type: 'SRV',
class: 'IN',
name: 'hello.srv.com',
data: {
port: 9090,
target: 'hello.target.com'
}
}, {
type: 'CNAME',
class: 'IN',
name: 'hello.cname.com',
data: 'hello.other.domain.com'
}]
})
testEncoder(t, packet, {
type: 'response',
id: 100,
flags: 0,
additionals: [{
type: 'AAAA',
name: 'hello.a.com',
data: 'fe80::1'
}, {
type: 'PTR',
name: 'hello.ptr.com',
data: 'hello.other.ptr.com'
}, {
type: 'SRV',
name: 'hello.srv.com',
ttl: 42,
data: {
port: 9090,
target: 'hello.target.com'
}
}],
answers: [{
type: 'NULL',
name: 'hello.null.com',
data: Buffer.from([1, 2, 3, 4, 5])
}]
})
testEncoder(t, packet, {
type: 'response',
answers: [{
type: 'TXT',
name: 'emptytxt.com',
data: ''
}]
})
t.end()
})
tape('rcode', function (t) {
const errors = ['NOERROR', 'FORMERR', 'SERVFAIL', 'NXDOMAIN', 'NOTIMP', 'REFUSED', 'YXDOMAIN', 'YXRRSET', 'NXRRSET', 'NOTAUTH', 'NOTZONE', 'RCODE_11', 'RCODE_12', 'RCODE_13', 'RCODE_14', 'RCODE_15']
for (const i in errors) {
const code = rcodes.toRcode(errors[i])
t.ok(errors[i] === rcodes.toString(code), 'rcode conversion from/to string matches: ' + rcodes.toString(code))
}
const ops = ['QUERY', 'IQUERY', 'STATUS', 'OPCODE_3', 'NOTIFY', 'UPDATE', 'OPCODE_6', 'OPCODE_7', 'OPCODE_8', 'OPCODE_9', 'OPCODE_10', 'OPCODE_11', 'OPCODE_12', 'OPCODE_13', 'OPCODE_14', 'OPCODE_15']
for (const j in ops) {
const ocode = opcodes.toOpcode(ops[j])
t.ok(ops[j] === opcodes.toString(ocode), 'opcode conversion from/to string matches: ' + opcodes.toString(ocode))
}
const buf = packet.encode({
type: 'response',
id: 45632,
flags: 0x8480,
answers: [{
type: 'A',
name: 'hello.example.net',
data: '127.0.0.1'
}]
})
const val = packet.decode(buf)
t.ok(val.type === 'response', 'decode type')
t.ok(val.opcode === 'QUERY', 'decode opcode')
t.ok(val.flag_qr === true, 'decode flag_qr')
t.ok(val.flag_aa === true, 'decode flag_aa')
t.ok(val.flag_tc === false, 'decode flag_tc')
t.ok(val.flag_rd === false, 'decode flag_rd')
t.ok(val.flag_ra === true, 'decode flag_ra')
t.ok(val.flag_z === false, 'decode flag_z')
t.ok(val.flag_ad === false, 'decode flag_ad')
t.ok(val.flag_cd === false, 'decode flag_cd')
t.ok(val.rcode === 'NOERROR', 'decode rcode')
t.end()
})
tape('name_encoding', function (t) {
let data = 'foo.example.com'
const buf = Buffer.allocUnsafe(255)
let offset = 0
packet.name.encode(data, buf, offset)
t.ok(packet.name.encode.bytes === 17, 'name encoding length matches')
let dd = packet.name.decode(buf, offset)
t.ok(data === dd, 'encode/decode matches')
offset += packet.name.encode.bytes
data = 'com'
packet.name.encode(data, buf, offset)
t.ok(packet.name.encode.bytes === 5, 'name encoding length matches')
dd = packet.name.decode(buf, offset)
t.ok(data === dd, 'encode/decode matches')
offset += packet.name.encode.bytes
data = 'example.com.'
packet.name.encode(data, buf, offset)
t.ok(packet.name.encode.bytes === 13, 'name encoding length matches')
dd = packet.name.decode(buf, offset)
t.ok(data.slice(0, -1) === dd, 'encode/decode matches')
offset += packet.name.encode.bytes
data = '.'
packet.name.encode(data, buf, offset)
t.ok(packet.name.encode.bytes === 1, 'name encoding length matches')
dd = packet.name.decode(buf, offset)
t.ok(data === dd, 'encode/decode matches')
t.end()
})
tape('name_decoding', function (t) {
// The two most significant bits of a valid label header must be either both zero or both one
t.throws(function () { packet.name.decode(Buffer.from([0x80])) }, /Cannot decode name \(bad label\)$/)
t.throws(function () { packet.name.decode(Buffer.from([0xb0])) }, /Cannot decode name \(bad label\)$/)
// Ensure there's enough buffer to read
t.throws(function () { packet.name.decode(Buffer.from([])) }, /Cannot decode name \(buffer overflow\)$/)
t.throws(function () { packet.name.decode(Buffer.from([0x01, 0x00])) }, /Cannot decode name \(buffer overflow\)$/)
t.throws(function () { packet.name.decode(Buffer.from([0x01])) }, /Cannot decode name \(buffer overflow\)$/)
t.throws(function () { packet.name.decode(Buffer.from([0xc0])) }, /Cannot decode name \(buffer overflow\)$/)
// Allow only pointers backwards
t.throws(function () { packet.name.decode(Buffer.from([0xc0, 0x00])) }, /Cannot decode name \(bad pointer\)$/)
t.throws(function () { packet.name.decode(Buffer.from([0xc0, 0x01])) }, /Cannot decode name \(bad pointer\)$/)
// A name can be only 253 characters (when connected with dots)
const maxLength = Buffer.alloc(255)
maxLength.fill(Buffer.from([0x01, 0x61]), 0, 254)
t.ok(packet.name.decode(maxLength) === new Array(127).fill('a').join('.'))
const tooLong = Buffer.alloc(256)
tooLong.fill(Buffer.from([0x01, 0x61]))
t.throws(function () { packet.name.decode(tooLong) }, /Cannot decode name \(name too long\)$/)
// Ensure jumps don't reset the total length counter
const tooLongWithJump = Buffer.alloc(403)
tooLongWithJump.fill(Buffer.from([0x01, 0x61]), 0, 200)
tooLongWithJump.fill(Buffer.from([0x01, 0x61]), 201, 401)
tooLongWithJump.set([0xc0, 0x00], 401)
t.throws(function () { packet.name.decode(tooLongWithJump, 201) }, /Cannot decode name \(name too long\)$/)
// Ensure a jump to a null byte doesn't add extra dots
t.ok(packet.name.decode(Buffer.from([0x00, 0x01, 0x61, 0xc0, 0x00]), 1) === 'a')
// Ensure deeply nested pointers don't cause "Maximum call stack size exceeded" errors
const buf = Buffer.alloc(16386)
for (let i = 0; i < 16384; i += 2) {
buf.writeUInt16BE(0xc000 | i, i + 2)
}
t.ok(packet.name.decode(buf, 16384) === '.')
t.end()
})
tape('stream', function (t) {
const val = {
type: 'query',
id: 45632,
flags: 0x8480,
answers: [{
type: 'A',
name: 'test2.example.net',
data: '198.51.100.1'
}]
}
const buf = packet.streamEncode(val)
const val2 = packet.streamDecode(buf)
t.same(buf.length, packet.streamEncode.bytes, 'streamEncode.bytes was set correctly')
t.ok(compare(t, val2.type, val.type), 'streamDecoded type match')
t.ok(compare(t, val2.id, val.id), 'streamDecoded id match')
t.ok(parseInt(val2.flags) === parseInt(val.flags & 0x7FFF), 'streamDecoded flags match')
const answer = val.answers[0]
const answer2 = val2.answers[0]
t.ok(compare(t, answer.type, answer2.type), 'streamDecoded RR type match')
t.ok(compare(t, answer.name, answer2.name), 'streamDecoded RR name match')
t.ok(compare(t, answer.data, answer2.data), 'streamDecoded RR rdata match')
t.end()
})
tape('opt', function (t) {
const val = {
type: 'query',
questions: [{
type: 'A',
name: 'hello.a.com'
}],
additionals: [{
type: 'OPT',
name: '.',
udpPayloadSize: 1024
}]
}
testEncoder(t, packet, val)
let buf = packet.encode(val)
let val2 = packet.decode(buf)
const additional1 = val.additionals[0]
let additional2 = val2.additionals[0]
t.ok(compare(t, additional1.name, additional2.name), 'name matches')
t.ok(compare(t, additional1.udpPayloadSize, additional2.udpPayloadSize), 'udp payload size matches')
t.ok(compare(t, 0, additional2.flags), 'flags match')
additional1.flags = packet.DNSSEC_OK
additional1.extendedRcode = 0x80
additional1.options = [{
code: 'CLIENT_SUBNET', // edns-client-subnet, see RFC 7871
ip: 'fe80::',
sourcePrefixLength: 64
}, {
code: 8, // still ECS
ip: '5.6.0.0',
sourcePrefixLength: 16,
scopePrefixLength: 16
}, {
code: 'padding',
length: 31
}, {
code: 'TCP_KEEPALIVE'
}, {
code: 'tcp_keepalive',
timeout: 150
}, {
code: 'KEY_TAG',
tags: [1, 82, 987]
}]
buf = packet.encode(val)
val2 = packet.decode(buf)
additional2 = val2.additionals[0]
t.ok(compare(t, 1 << 15, additional2.flags), 'DO bit set in flags')
t.ok(compare(t, true, additional2.flag_do), 'DO bit set')
t.ok(compare(t, additional1.extendedRcode, additional2.extendedRcode), 'extended rcode matches')
t.ok(compare(t, 8, additional2.options[0].code))
t.ok(compare(t, 'fe80::', additional2.options[0].ip))
t.ok(compare(t, 64, additional2.options[0].sourcePrefixLength))
t.ok(compare(t, '5.6.0.0', additional2.options[1].ip))
t.ok(compare(t, 16, additional2.options[1].sourcePrefixLength))
t.ok(compare(t, 16, additional2.options[1].scopePrefixLength))
t.ok(compare(t, additional1.options[2].length, additional2.options[2].data.length))
t.ok(compare(t, additional1.options[3].timeout, undefined))
t.ok(compare(t, additional1.options[4].timeout, additional2.options[4].timeout))
t.ok(compare(t, additional1.options[5].tags, additional2.options[5].tags))
t.end()
})
tape('dnskey', function (t) {
testEncoder(t, packet.dnskey, {
flags: packet.dnskey.SECURE_ENTRYPOINT | packet.dnskey.ZONE_KEY,
algorithm: 1,
key: Buffer.from([0, 1, 2, 3, 4, 5])
})
t.end()
})
tape('rrsig', function (t) {
const testRRSIG = {
typeCovered: 'A',
algorithm: 1,
labels: 2,
originalTTL: 3600,
expiration: 1234,
inception: 1233,
keyTag: 2345,
signersName: 'foo.com',
signature: Buffer.from([0, 1, 2, 3, 4, 5])
}
testEncoder(t, packet.rrsig, testRRSIG)
// Check the signature length is correct with extra junk at the end
const buf = Buffer.allocUnsafe(packet.rrsig.encodingLength(testRRSIG) + 4)
packet.rrsig.encode(testRRSIG, buf)
const val2 = packet.rrsig.decode(buf)
t.ok(compare(t, testRRSIG, val2))
t.end()
})
tape('rrp', function (t) {
testEncoder(t, packet.rp, {
mbox: 'foo.bar.com',
txt: 'baz.bar.com'
})
testEncoder(t, packet.rp, {
mbox: 'foo.bar.com'
})
testEncoder(t, packet.rp, {
txt: 'baz.bar.com'
})
testEncoder(t, packet.rp, {})
t.end()
})
tape('nsec', function (t) {
testEncoder(t, packet.nsec, {
nextDomain: 'foo.com',
rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV']
})
testEncoder(t, packet.nsec, {
nextDomain: 'foo.com',
rrtypes: ['TXT'] // 16
})
testEncoder(t, packet.nsec, {
nextDomain: 'foo.com',
rrtypes: ['TKEY'] // 249
})
testEncoder(t, packet.nsec, {
nextDomain: 'foo.com',
rrtypes: ['RRSIG', 'NSEC']
})
testEncoder(t, packet.nsec, {
nextDomain: 'foo.com',
rrtypes: ['TXT', 'RRSIG']
})
testEncoder(t, packet.nsec, {
nextDomain: 'foo.com',
rrtypes: ['TXT', 'NSEC']
})
// Test with the sample NSEC from https://tools.ietf.org/html/rfc4034#section-4.3
const sampleNSEC = new Uint8Array(Buffer.from('003704686f7374076578616d706c6503636f6d00' +
'0006400100000003041b000000000000000000000000000000000000000000000' +
'000000020', 'hex'))
const decoded = packet.nsec.decode(sampleNSEC)
t.ok(compare(t, decoded, {
nextDomain: 'host.example.com',
rrtypes: ['A', 'MX', 'RRSIG', 'NSEC', 'UNKNOWN_1234']
}))
const reencoded = packet.nsec.encode(decoded)
t.same(sampleNSEC.length, reencoded.length)
t.same(sampleNSEC, reencoded)
t.end()
})
tape('nsec3', function (t) {
testEncoder(t, packet.nsec3, {
algorithm: 1,
flags: 0,
iterations: 257,
salt: Buffer.from([42, 42, 42]),
nextDomain: Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
rrtypes: ['A', 'DNSKEY', 'CAA', 'DLV']
})
t.end()
})
tape('ds', function (t) {
testEncoder(t, packet.ds, {
keyTag: 1234,
algorithm: 1,
digestType: 1,
digest: Buffer.from([0, 1, 2, 3, 4, 5])
})
t.end()
})
tape('unpack', function (t) {
const buf = Buffer.from([
0x00, 0x79,
0xde, 0xad, 0x85, 0x00, 0x00, 0x01, 0x00, 0x01,
0x00, 0x02, 0x00, 0x02, 0x02, 0x6f, 0x6a, 0x05,
0x62, 0x61, 0x6e, 0x67, 0x6a, 0x03, 0x63, 0x6f,
0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c,
0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10,
0x00, 0x04, 0x81, 0xfa, 0x0b, 0xaa, 0xc0, 0x0f,
0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10,
0x00, 0x05, 0x02, 0x63, 0x6a, 0xc0, 0x0f, 0xc0,
0x0f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x0e,
0x10, 0x00, 0x02, 0xc0, 0x0c, 0xc0, 0x3a, 0x00,
0x01, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00,
0x04, 0x45, 0x4d, 0x9b, 0x9c, 0xc0, 0x0c, 0x00,
0x1c, 0x00, 0x01, 0x00, 0x00, 0x0e, 0x10, 0x00,
0x10, 0x20, 0x01, 0x04, 0x18, 0x00, 0x00, 0x50,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf9
])
const val = packet.streamDecode(buf)
const answer = val.answers[0]
const authority = val.authorities[1]
t.ok(val.rcode === 'NOERROR', 'decode rcode')
t.ok(compare(t, answer.type, 'A'), 'streamDecoded RR type match')
t.ok(compare(t, answer.name, 'oj.bangj.com'), 'streamDecoded RR name match')
t.ok(compare(t, answer.data, '129.250.11.170'), 'streamDecoded RR rdata match')
t.ok(compare(t, authority.type, 'NS'), 'streamDecoded RR type match')
t.ok(compare(t, authority.name, 'bangj.com'), 'streamDecoded RR name match')
t.ok(compare(t, authority.data, 'oj.bangj.com'), 'streamDecoded RR rdata match')
t.end()
})
tape('optioncodes', function (t) {
const opts = [
[0, 'OPTION_0'],
[1, 'LLQ'],
[2, 'UL'],
[3, 'NSID'],
[4, 'OPTION_4'],
[5, 'DAU'],
[6, 'DHU'],
[7, 'N3U'],
[8, 'CLIENT_SUBNET'],
[9, 'EXPIRE'],
[10, 'COOKIE'],
[11, 'TCP_KEEPALIVE'],
[12, 'PADDING'],
[13, 'CHAIN'],
[14, 'KEY_TAG'],
[26946, 'DEVICEID'],
[65535, 'OPTION_65535'],
[64000, 'OPTION_64000'],
[65002, 'OPTION_65002'],
[-1, null]
]
for (const [code, str] of opts) {
const s = optioncodes.toString(code)
t.ok(compare(t, s, str), `${code} => ${str}`)
t.ok(compare(t, optioncodes.toCode(s), code), `${str} => ${code}`)
}
t.ok(compare(t, optioncodes.toCode('INVALIDINVALID'), -1))
t.end()
})
tape('packet exported codec', function (t) {
const input = {
type: 'query',
questions: [{
type: 'A',
name: 'hello.a.com',
class: 'IN'
}]
}
packet.encode.bytes = 0
t.equals(packet.packet.encode.bytes, 0)
t.equals(packet.packet.encode.bytes, packet.encode.bytes)
const buf = packet.packet.encode(input)
t.equals(packet.packet.encode.bytes, 29)
t.equals(packet.packet.encode.bytes, packet.encode.bytes)
t.deepEqual(
buf,
packet.encode(input)
)
packet.decode.bytes = 0
t.equals(packet.packet.decode.bytes, 0)
t.equals(packet.packet.decode.bytes, packet.decode.bytes)
const obj = packet.packet.decode(buf)
t.equals(packet.packet.decode.bytes, 29)
t.equals(packet.packet.decode.bytes, packet.decode.bytes)
t.deepEqual(
obj,
packet.decode(buf)
)
t.end()
})
tape('single query error with multiple questions', function (t) {
t.throws(() => {
packet.query.encode({
questions: []
})
}, /Only one .question object expected instead of a .questions array!/)
t.end()
})
tape('single query -> response encoding', function (t) {
const question = {
type: 'A',
name: 'hello.a.com',
class: 'IN'
}
const length = packet.query.encodingLength({ question })
t.equals(length, 29)
t.equals(packet.query.encode.bytes, 0)
const queryBytes = packet.query.encode({
question
})
const decodedQuestion = packet.decode(queryBytes)
t.equals(packet.query.encode.bytes, length)
t.equal(decodedQuestion.type, 'query')
t.deepEqual(decodedQuestion.questions, [question])
t.equals(packet.query.decode.bytes, 0)
decodedQuestion.question = decodedQuestion.questions[0]
delete decodedQuestion.questions
t.deepEqual(packet.query.decode(queryBytes), decodedQuestion)
const responseBytes = packet.encode({
type: 'response',
questions: [question]
})
const decodedResponse = packet.response.decode(responseBytes)
t.equal(packet.response.encodingLength(decodedResponse), length)
t.equals(packet.response.decode.bytes, length)
t.deepEqual(decodedResponse.question, question)
t.deepEqual(packet.response.encode(decodedResponse), responseBytes)
t.end()
})
test('buffer utf8', function (sub) {
for (const [index, fixture] of [
// '', // empty
'basic: hi',
'japanese: 日本語',
'mixed: 日本語 hi',
'min 1 byte: \x00',
'odd 1 byte: \x4c',
'max 1 byte: \x7f',
'min 2 byte: \x80',
'odd 2 byte: \xd941',
'max 2 byte: \x7fff',
'min 3 byte: \x8000',
'odd 3 byte: \xa158',
'max 3 byte: \xffff',
`4 byte: ${String.fromCodePoint(100000)}`
].entries()) {
sub.test(`fixture #${index}`, t => {
const check = Buffer.from(fixture)
const checkHex = check.toString('hex')
const len = check.length
t.equals(bytelength(fixture), len, `fixture ${fixture} length`)
const buf = new Uint8Array(len)
t.equals(write(buf, fixture, 0), len, 'write.num')
t.equals(toHex(buf, 0, len), checkHex, `write: ${fixture}`)
t.equals(check.compare(Buffer.from(writeHex(buf, checkHex, 0, hexLength(checkHex)))), 0)
t.equals(toUtf8(check, 0, check.length), check.toString(), `toUtf8: ${fixture}`)
t.end()
})
}
sub.test('surrogate pairs', function (t) {
[
[0xd821, 0xdea0],
[0xd800, 0xdc00],
[0xd801, 0xdc01],
[0xddff, 0xdfff],
[0xd821, 0x0000],
[0xd821, 0xd821, 0xdea0]
].forEach(function (bytes, index) {
const str = String.fromCharCode(...bytes)
const buf = new Uint8Array(bytelength(str))
const check = Buffer.from(str)
t.equal(buf.length, check.length)
write(buf, str, 0)
t.equal(toHex(buf, 0, buf.length), check.toString('hex'), `#${index} [${bytes}].toHex() ... ${check.toString('hex')}`)
t.equal(toUtf8(buf, 0, buf.length), check.toString(), `#${index} [${bytes}].toUtf8`)
})
t.end()
})
sub.test('all code points', function (t) {
const blockSize = 2048
const blocks = 65536 / blockSize
let code = 0
for (let block = 0; block < blocks; block += 1) {
const expected = {}
const actual = {}
for (let i = 0; i < blockSize; i += 1, code += 1) {
const str = String.fromCharCode(code)
const buf = new Uint8Array(bytelength(str))
write(buf, str, 0)
const exp = Buffer.from(str)
expected[code] = exp.toString('hex')
actual[code] = toHex(buf, 0, buf.length)
}
t.same(actual, expected)
}
t.end()
})
})
function testEncoder (t, rpacket, val) {
const buf = rpacket.encode(val)
const val2 = rpacket.decode(buf)
t.same(buf.length, rpacket.encode.bytes, 'encode.bytes was set correctly')
t.same(buf.length, rpacket.encodingLength(val), 'encoding length matches')
t.ok(compare(t, val, val2), 'decoded object match')
const buf2 = rpacket.encode(val2)
const val3 = rpacket.decode(buf2)
t.same(buf2.length, rpacket.encode.bytes, 'encode.bytes was set correctly on re-encode')
t.same(buf2.length, rpacket.encodingLength(val), 'encoding length matches on re-encode')
t.ok(compare(t, val, val3), 'decoded object match on re-encode')
t.ok(compare(t, val2, val3), 're-encoded decoded object match on re-encode')
const bigger = Buffer.allocUnsafe(buf2.length + 10)
const buf3 = rpacket.encode(val, bigger, 10)
const val4 = rpacket.decode(buf3, 10)
t.ok(buf3 === bigger, 'echoes buffer on external buffer')
t.same(rpacket.encode.bytes, buf.length, 'encode.bytes is the same on external buffer')
t.ok(compare(t, val, val4), 'decoded object match on external buffer')
}
function compare (t, a, b) {
if (a instanceof Uint8Array) return toHex(a, 0, a.length) === toHex(b, 0, b.length)
if (typeof a === 'object' && a && b) {
const keys = Object.keys(a)
for (let i = 0; i < keys.length; i++) {
if (!compare(t, a[keys[i]], b[keys[i]])) {
return false
}
}
} else if (Array.isArray(b) && !Array.isArray(a)) {
// TXT always decode as array
return a.toString() === b[0].toString()
} else {
return a === b
}
return true
}
+47
View File
@@ -0,0 +1,47 @@
export type RecordType = "A"
| "AAAA"
| "AFSDB"
| "APL"
| "AXFR"
| "CAA"
| "CDNSKEY"
| "CDS"
| "CERT"
| "CNAME"
| "DNAME"
| "DHCID"
| "DLV"
| "DNSKEY"
| "DS"
| "HINFO"
| "HIP"
| "IXFR"
| "IPSECKEY"
| "KEY"
| "KX"
| "LOC"
| "MX"
| "NAPTR"
| "NS"
| "NSEC"
| "NSEC3"
| "NSEC3PARAM"
| "NULL"
| "OPT"
| "PTR"
| "RRSIG"
| "RP"
| "SIG"
| "SOA"
| "SRV"
| "SSHFP"
| "TA"
| "TKEY"
| "TLSA"
| "TSIG"
| "TXT"
| "URI"
| string;
export function toString (type: number): RecordType;
export function toType (name: RecordType): number;
+196
View File
@@ -0,0 +1,196 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.toString = toString;
exports.toType = toType;
function toString(type) {
switch (type) {
case 1:
return 'A';
case 10:
return 'NULL';
case 28:
return 'AAAA';
case 18:
return 'AFSDB';
case 42:
return 'APL';
case 257:
return 'CAA';
case 60:
return 'CDNSKEY';
case 59:
return 'CDS';
case 37:
return 'CERT';
case 5:
return 'CNAME';
case 49:
return 'DHCID';
case 32769:
return 'DLV';
case 39:
return 'DNAME';
case 48:
return 'DNSKEY';
case 43:
return 'DS';
case 55:
return 'HIP';
case 13:
return 'HINFO';
case 45:
return 'IPSECKEY';
case 25:
return 'KEY';
case 36:
return 'KX';
case 29:
return 'LOC';
case 15:
return 'MX';
case 35:
return 'NAPTR';
case 2:
return 'NS';
case 47:
return 'NSEC';
case 50:
return 'NSEC3';
case 51:
return 'NSEC3PARAM';
case 12:
return 'PTR';
case 46:
return 'RRSIG';
case 17:
return 'RP';
case 24:
return 'SIG';
case 6:
return 'SOA';
case 99:
return 'SPF';
case 33:
return 'SRV';
case 44:
return 'SSHFP';
case 32768:
return 'TA';
case 249:
return 'TKEY';
case 52:
return 'TLSA';
case 250:
return 'TSIG';
case 16:
return 'TXT';
case 252:
return 'AXFR';
case 251:
return 'IXFR';
case 41:
return 'OPT';
case 255:
return 'ANY';
}
return 'UNKNOWN_' + type;
}
function toType(name) {
switch (name.toUpperCase()) {
case 'A':
return 1;
case 'NULL':
return 10;
case 'AAAA':
return 28;
case 'AFSDB':
return 18;
case 'APL':
return 42;
case 'CAA':
return 257;
case 'CDNSKEY':
return 60;
case 'CDS':
return 59;
case 'CERT':
return 37;
case 'CNAME':
return 5;
case 'DHCID':
return 49;
case 'DLV':
return 32769;
case 'DNAME':
return 39;
case 'DNSKEY':
return 48;
case 'DS':
return 43;
case 'HIP':
return 55;
case 'HINFO':
return 13;
case 'IPSECKEY':
return 45;
case 'KEY':
return 25;
case 'KX':
return 36;
case 'LOC':
return 29;
case 'MX':
return 15;
case 'NAPTR':
return 35;
case 'NS':
return 2;
case 'NSEC':
return 47;
case 'NSEC3':
return 50;
case 'NSEC3PARAM':
return 51;
case 'PTR':
return 12;
case 'RRSIG':
return 46;
case 'RP':
return 17;
case 'SIG':
return 24;
case 'SOA':
return 6;
case 'SPF':
return 99;
case 'SRV':
return 33;
case 'SSHFP':
return 44;
case 'TA':
return 32768;
case 'TKEY':
return 249;
case 'TLSA':
return 52;
case 'TSIG':
return 250;
case 'TXT':
return 16;
case 'AXFR':
return 252;
case 'IXFR':
return 251;
case 'OPT':
return 41;
case 'ANY':
return 255;
case '*':
return 255;
}
if (name.toUpperCase().startsWith('UNKNOWN_')) return parseInt(name.slice(8));
return 0;
}
+101
View File
@@ -0,0 +1,101 @@
export function toString (type) {
switch (type) {
case 1: return 'A'
case 10: return 'NULL'
case 28: return 'AAAA'
case 18: return 'AFSDB'
case 42: return 'APL'
case 257: return 'CAA'
case 60: return 'CDNSKEY'
case 59: return 'CDS'
case 37: return 'CERT'
case 5: return 'CNAME'
case 49: return 'DHCID'
case 32769: return 'DLV'
case 39: return 'DNAME'
case 48: return 'DNSKEY'
case 43: return 'DS'
case 55: return 'HIP'
case 13: return 'HINFO'
case 45: return 'IPSECKEY'
case 25: return 'KEY'
case 36: return 'KX'
case 29: return 'LOC'
case 15: return 'MX'
case 35: return 'NAPTR'
case 2: return 'NS'
case 47: return 'NSEC'
case 50: return 'NSEC3'
case 51: return 'NSEC3PARAM'
case 12: return 'PTR'
case 46: return 'RRSIG'
case 17: return 'RP'
case 24: return 'SIG'
case 6: return 'SOA'
case 99: return 'SPF'
case 33: return 'SRV'
case 44: return 'SSHFP'
case 32768: return 'TA'
case 249: return 'TKEY'
case 52: return 'TLSA'
case 250: return 'TSIG'
case 16: return 'TXT'
case 252: return 'AXFR'
case 251: return 'IXFR'
case 41: return 'OPT'
case 255: return 'ANY'
}
return 'UNKNOWN_' + type
}
export function toType (name) {
switch (name.toUpperCase()) {
case 'A': return 1
case 'NULL': return 10
case 'AAAA': return 28
case 'AFSDB': return 18
case 'APL': return 42
case 'CAA': return 257
case 'CDNSKEY': return 60
case 'CDS': return 59
case 'CERT': return 37
case 'CNAME': return 5
case 'DHCID': return 49
case 'DLV': return 32769
case 'DNAME': return 39
case 'DNSKEY': return 48
case 'DS': return 43
case 'HIP': return 55
case 'HINFO': return 13
case 'IPSECKEY': return 45
case 'KEY': return 25
case 'KX': return 36
case 'LOC': return 29
case 'MX': return 15
case 'NAPTR': return 35
case 'NS': return 2
case 'NSEC': return 47
case 'NSEC3': return 50
case 'NSEC3PARAM': return 51
case 'PTR': return 12
case 'RRSIG': return 46
case 'RP': return 17
case 'SIG': return 24
case 'SOA': return 6
case 'SPF': return 99
case 'SRV': return 33
case 'SSHFP': return 44
case 'TA': return 32768
case 'TKEY': return 249
case 'TLSA': return 52
case 'TSIG': return 250
case 'TXT': return 16
case 'AXFR': return 252
case 'IXFR': return 251
case 'OPT': return 41
case 'ANY': return 255
case '*': return 255
}
if (name.toUpperCase().startsWith('UNKNOWN_')) return parseInt(name.slice(8))
return 0
}
+371
View File
@@ -0,0 +1,371 @@
import { RecordType } from '../types.js';
import { RecordClass } from '../rcodes.js';
import { OptionCodes } from '../optioncodes.js';
import { OPCode } from '../opcodes.js';
export { RecordType } from '../types.js';
export { RecordClass } from '../rcodes.js';
export { OptionCodes } from '../optioncodes.js';
export { OPCode } from '../opcodes.js';
export interface Codec <Type> {
encode(package: Type, buf?: Uint8Array, offset?: number): Uint8Array;
decode(buf: Uint8Array, offset?: number): Type;
encodingLength(packet: Type): number;
}
export interface Question {
type: RecordType;
name: string;
class?: RecordClass | undefined;
}
export interface SrvData {
target: string;
port?: number;
priority?: number | undefined;
weight?: number | undefined;
}
export interface HInfoData {
cpu: string;
os: string;
}
export interface SoaData {
mname: string;
rname: string;
serial?: number | undefined;
refresh?: number | undefined;
retry?: number | undefined;
expire?: number | undefined;
minimum?: number | undefined;
}
export type TxtData = string | Uint8Array | Array<string | Uint8Array>;
export interface CaaData {
issuerCritical?: boolean | undefined;
flags?: number | undefined;
tag: string;
value: string;
}
export interface MxData {
preference?: number | undefined;
exchange: string;
}
export interface BaseAnswer<T, D> {
type: T;
name: string;
ttl?: number | undefined;
class?: RecordClass | undefined;
data: D;
}
/**
* Record types for which the library will provide a string in the data field.
*/
export type StringRecordType = "A" | "AAAA" | "CNAME" | "DNAME" | "NS" | "PTR";
/**
* Record types for which the library does not attempt to process the data
* field.
*/
export type OtherRecordType =
| "AFSDB"
| "APL"
| "AXFR"
| "CDNSKEY"
| "CDS"
| "CERT"
| "DHCID"
| "DLV"
| "HIP"
| "IXFR"
| "IPSECKEY"
| "KEY"
| "KX"
| "LOC"
| "NAPTR"
| "NSEC3PARAM"
| "SIG"
| "SSHFP"
| "TA"
| "TKEY"
| "TLSA"
| "TSIG"
| "URI";
export type StringAnswer = BaseAnswer<StringRecordType, string>;
export type SrvAnswer = BaseAnswer<"SRV", SrvData>;
export type HInfoAnswer = BaseAnswer<"HINFO", HInfoData>;
export type SoaAnswer = BaseAnswer<"SOA", SoaData>;
export type TxtAnswer = BaseAnswer<"TXT", TxtData>;
export type CaaAnswer = BaseAnswer<"CAA", CaaData>;
export type MxAnswer = BaseAnswer<"MX", MxData>;
export type NullAnswer = BaseAnswer<"NULL", Uint8Array>;
export type OptAnswer = BaseAnswer<"OPT", OptionData[]>;
export type DNSKeyAnswer = BaseAnswer<"DNSKEY", DNSKeyData>;
export type RRSigAnswer = BaseAnswer<"RRSIG", RRSigData>;
export type RPAnswer = BaseAnswer<"RP", RPData>;
export type NSecAnswer = BaseAnswer<"NSEC", NSecData>;
export type NSec3Answer = BaseAnswer<"NSEC3", NSec3Data>;
export type DSAnswer = BaseAnswer<"DS", DigestData>;
export type BufferAnswer = BaseAnswer<OtherRecordType, Uint8Array>;
export type Answer =
| StringAnswer
| SrvAnswer
| HInfoAnswer
| SoaAnswer
| TxtAnswer
| CaaAnswer
| MxAnswer
| NullAnswer
| OptAnswer
| DNSKeyAnswer
| RRSigAnswer
| RPAnswer
| NSecAnswer
| NSec3Answer
| DSAnswer
| BufferAnswer;
export interface Packet {
/**
* Whether the packet is a query or a response. This field may be
* omitted if it is clear from the context of usage what type of packet
* it is.
*/
type?: "query" | "response" | undefined;
id?: number | undefined;
/**
* A bit-mask combination of zero or more of:
* {@link AUTHORITATIVE_ANSWER},
* {@link TRUNCATED_RESPONSE},
* {@link RECURSION_DESIRED},
* {@link RECURSION_AVAILABLE},
* {@link AUTHENTIC_DATA},
* {@link CHECKING_DISABLED}.
*/
flags?: number | undefined;
questions?: Question[] | undefined;
answers?: Answer[] | undefined;
additionals?: Answer[] | undefined;
authorities?: Answer[] | undefined;
flag_qr?: boolean;
opcode?: OPCode;
flag_aa?: boolean;
flag_tc?: boolean;
flag_rd?: boolean;
flag_ra?: boolean;
flag_z?: boolean;
flag_ad?: boolean;
flag_cd?: boolean;
rcode?: RecordClass;
}
export const DNSSEC_OK: 32768;
export const AUTHORITATIVE_ANSWER: 1024;
export const TRUNCATED_RESPONSE: 512;
export const RECURSION_DESIRED: 256;
export const RECURSION_AVAILABLE: 128;
export const AUTHENTIC_DATA: 32;
export const CHECKING_DISABLED: 16;
export interface DNSKeyData {
key: string;
flags: number;
algorithm: number;
}
export interface DigestData {
digest: Uint8Array;
keyTag: number;
algorithm: number;
digestType: number;
}
export type SSHFPFingerPrintLength = 20 | 32;
export type SSHFPHash = 1 | 2;
export type SSHFPFingerPrintLengthFor <T extends SSHFPHash> = T extends 1 ? 20 : 32;
export interface SSHFP {
algorithm: number;
hash: SSHFPHash;
fingerprint: string;
}
export interface NSecData {
nextDomain: Uint8Array;
rrtypes: RecordType[];
}
export interface NSec3Data {
salt: Uint8Array;
nextDomain: Uint8Array;
algorithm: number;
flags: number;
iterations: number;
rrtypes: RecordType[];
}
export type ACodec = Codec<string>;
export type PtrCodec = Codec<Uint8Array>;
export type TxtCodec = Codec<Uint8Array[]>;
export type NullCodec = Codec<Uint8Array>;
export type CaaCodec = Codec<string> & {
ISSUER_CRITICAL: 128
};
export type DNSKeyCodec = Codec<DNSKeyData> & {
PROTOCOL_DNSSEC: 3
ZONE_KEY: 0x80
SECURE_ENTRYPOINT: 0x8000
};
export type DSCodec = Codec<DigestData>;
export type SSHFPCodec = Codec<SSHFP> & {
getFingerprintLengthForHashType(hashType: SSHFPHash): SSHFPFingerPrintLength;
};
export type HInfoCodec = Codec<HInfoData>;
export type AAAACodec = Codec<string>;
export type UnknownCodec = Codec<Uint8Array>;
export type SrvCodec = Codec<SrvData>;
export type NSCodec = Codec<string>;
export type SoaCodec = Codec<SoaData>;
export type MxCodec = Codec<MxData>;
export type OptCodec = Codec<OptionData[]>;
export type QuestionCodec = Codec<Question>;
export type RRSigCodec = Codec<RRSigData>;
export type RPCodec = Codec<RPData>;
export type NSecCodec = Codec<NSecData>;
export type NSec3Codec = Codec<NSec3Data>;
export type PacketCodec = Codec<Packet>;
export type SingleQuestionPacket = Omit<Packet, 'questions'> & {
question: Question
};
export type QueryCodec = Codec<SingleQuestionPacket>;
export type ResponseCodec = Codec<SingleQuestionPacket>;
export const a: ACodec;
export const caa: CaaCodec;
export const ptr: PtrCodec;
export const cname: PtrCodec;
export const dname: PtrCodec;
export const dnskey: DNSKeyCodec;
export const ds: DSCodec;
export const sshfp: SSHFPCodec;
export const hinfo: HInfoCodec;
export const aaaa: AAAACodec;
export const answer: Codec<Answer>;
export const txt: TxtCodec;
export const unknown: UnknownCodec;
export const mx: MxCodec;
export const name: Codec<string>;
export const ns: NSCodec;
export const soa: SoaCodec;
export const srv: SrvCodec;
export const option: Codec<OptionData>;
export const opt: OptCodec;
export const question: QuestionCodec;
export const rrsig: RRSigCodec;
export const rp: RPCodec;
export const nsec: NSecCodec;
export const nsec3: NSec3Codec;
export const packet: PacketCodec;
export const query: QueryCodec;
export const response: ResponseCodec;
declare const rnull: NullCodec;
export { rnull as null };
export interface GenericOptionData {
code: OptionCodes;
data: Uint8Array;
}
export interface ClientSubnetOptionData {
code: 'CLIENT_SUBNET';
ip: string;
family?: 1 | 2;
sourcePrefixLength?: number;
scopePrefixLength?: number;
}
export interface TCPKeepaliveOptionData {
code: 'TCP_KEEPALIVE';
timeout?: number;
}
export interface PaddingOptionData {
code: 'PADDING';
length?: number;
}
export interface KeyTagOptionData {
code: 'KEY_TAG';
tags: number[];
}
export type OptionData =
GenericOptionData
| ClientSubnetOptionData
| TCPKeepaliveOptionData
| PaddingOptionData
| KeyTagOptionData;
export interface RRSigData {
signature: Uint8Array;
typeCovered: RecordType;
algorithm: number;
labels: number;
originalTTL: number;
expiration: number;
inception: number;
keyTag: number;
signersName: string;
}
export interface RPData {
mbox?: string;
txt?: string;
}
export type AnyTypeCodec =
ACodec | PtrCodec | TxtCodec | NullCodec | AAAACodec |
SrvCodec | HInfoCodec | CaaCodec | NSCodec | SoaCodec |
MxCodec | OptCodec | RRSigCodec | RPCodec | NSecCodec |
NSec3Codec | DSCodec | UnknownCodec;
export type TypeCodec <Type> =
Type extends string ?
Uppercase<Type> extends 'A' ? ACodec :
Uppercase<Type> extends 'PTR' ? PtrCodec :
Uppercase<Type> extends 'CNAME' ? PtrCodec :
Uppercase<Type> extends 'DNAME' ? PtrCodec :
Uppercase<Type> extends 'TXT' ? TxtCodec :
Uppercase<Type> extends 'NULL' ? NullCodec :
Uppercase<Type> extends 'AAAA' ? AAAACodec :
Uppercase<Type> extends 'SRV' ? SrvCodec :
Uppercase<Type> extends 'HINFO' ? HInfoCodec :
Uppercase<Type> extends 'CAA' ? CaaCodec :
Uppercase<Type> extends 'NS' ? NSCodec :
Uppercase<Type> extends 'SOA' ? SoaCodec :
Uppercase<Type> extends 'MX' ? MxCodec :
Uppercase<Type> extends 'OPT' ? OptCodec :
Uppercase<Type> extends 'DNSKEY' ? DNSKeyCodec :
Uppercase<Type> extends 'RRSIG' ? RRSigCodec :
Uppercase<Type> extends 'RP' ? RPCodec :
Uppercase<Type> extends 'NSEC' ? NSecCodec :
Uppercase<Type> extends 'NSEC3' ? NSec3Codec :
Uppercase<Type> extends 'DS' ? DSCodec :
UnknownCodec
: AnyTypeCodec;
export function enc <Type>(type: Type): TypeCodec<Type>;
export function encode(packet: Packet, buf?: Uint8Array, offset?: number): Uint8Array;
export function decode(buf: Uint8Array, offset?: number): Packet;
export function encodingLength(packet: Packet): number;
export function decodeList <T= any>(list: T[], codec: Codec<T>, buf: Uint8Array, offset?: number): Packet[];
export function encodeList <T= any>(list: T[], codec: Codec<T>, buf?: Uint8Array, offset?: number): number;
export function encodingLengthList <T= any>(list: T[], codec: Codec<T>): number;
export function streamDecode(buffer: Uint8Array): Packet | null;
export function streamEncode(packet: Packet): Uint8Array;
+72
View File
@@ -0,0 +1,72 @@
/* tslint:disable:no-duplicate-imports */
import * as dnsPacket from '@leichtgewicht/dns-packet';
import { Codec, UnknownCodec } from '@leichtgewicht/dns-packet';
dnsPacket.decode(dnsPacket.encode({
id: 1,
type: 'query',
questions: [
{
name: 'test',
type: 'A'
},
{
name: 'foo',
type: 'boo'
}
]
}));
const num: number[] = [
dnsPacket.AUTHENTIC_DATA,
dnsPacket.AUTHORITATIVE_ANSWER,
dnsPacket.CHECKING_DISABLED,
dnsPacket.DNSSEC_OK,
dnsPacket.RECURSION_AVAILABLE,
dnsPacket.RECURSION_DESIRED,
dnsPacket.TRUNCATED_RESPONSE,
dnsPacket.dnskey.PROTOCOL_DNSSEC,
dnsPacket.dnskey.ZONE_KEY,
dnsPacket.dnskey.SECURE_ENTRYPOINT,
dnsPacket.encodingLength({}),
dnsPacket.encodingLengthList([], dnsPacket.a)
];
const codec: Array<Codec<any>> = [
dnsPacket.a,
dnsPacket.aaaa,
dnsPacket.answer,
dnsPacket.caa,
dnsPacket.cname,
dnsPacket.dname,
dnsPacket.dnskey,
dnsPacket.ds,
dnsPacket.hinfo,
dnsPacket.mx,
dnsPacket.name,
dnsPacket.ns,
dnsPacket.sshfp,
dnsPacket.nsec,
dnsPacket.nsec3,
dnsPacket.null,
dnsPacket.opt,
dnsPacket.option,
dnsPacket.ptr,
dnsPacket.question,
dnsPacket.rp,
dnsPacket.rrsig,
dnsPacket.soa,
dnsPacket.srv,
dnsPacket.txt,
dnsPacket.unknown
];
const unknownCodecs: UnknownCodec[] = [
dnsPacket.unknown,
dnsPacket.enc('hello')
];
dnsPacket.decode(new Uint8Array([])); // $ExpectType Packet
dnsPacket.decodeList([], dnsPacket.a, new Uint8Array(0)); // $ExpectType Packet[]
dnsPacket.streamDecode(new Uint8Array(0)); // $ExpectType Packet | null
dnsPacket.streamEncode({}); // $ExpectType Uint8Array
+21
View File
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"lib": [
"es6"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"baseUrl": "../",
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"@leichtgewicht/dns-packet": ["./types/index.d.ts"],
"@leichtgewicht/dns-packet/optioncodes.js": ["./optioncodes.d.ts"],
"@leichtgewicht/dns-packet/types.js": ["./types.d.ts"],
"@leichtgewicht/dns-packet/rcodes.js": ["./rcodes.d.ts"]
}
}
}