Skip to content
LogoLogo

@portable/dns-packet

Functions

DnsPacket.decode

Decodes a DNS message from its wire format. Compression pointers are followed, and each must point backwards, so a pointer loop can't hang the decoder. Bytes after the message are ignored. Throws TruncatedError when the input ends early (with framing: 'tcp', also when the whole message hasn't arrived yet) and InvalidInputError for malformed input.

function decode(
  bytes: Uint8Array,
  options: decode.Options = {},
): DecodedPacket;
OptionTypeDefaultDescription
framing"none" | "tcp"'none''tcp' reads the 2-byte length prefix used over TCP and TLS first.
import * as DnsPacket from '@portable/dns-packet';
 
const response = await fetch('https://cloudflare-dns.com/dns-query', {
  method: 'POST',
  headers: { 'content-type': 'application/dns-message' },
  body: query,
});
const packet = DnsPacket.decode(
  new Uint8Array(await response.arrayBuffer()),
);
for (const answer of packet.answers) {
  if (answer.type === 'A') {
    console.log(answer.data);
  }
}

DnsPacket.encode

Encodes a DNS message into its wire format, as sent over UDP or as a DNS-over-HTTPS body. Throws InvalidInputError for a name, address or record that can't be encoded, and UnsupportedError for an EDNS option with no data and no built-in encoder.

function encode(
  packet: Packet,
  options: encode.Options = {},
): Uint8Array<ArrayBuffer>;
OptionTypeDefaultDescription
compressbooleanfalseReplace repeated names with pointers to their first copy (RFC 1035 section 4.1.4). Applies to owner names and to names inside NS, CNAME, PTR, MX and SOA records.
framing"none" | "tcp"'none''tcp' adds the 2-byte length prefix used over TCP and TLS (RFC 1035 section 4.2.2).
import * as DnsPacket from '@portable/dns-packet';
 
const query = DnsPacket.encode({
  id: 1,
  flags: { rd: true },
  questions: [{ name: 'example.com', type: 'A' }],
});

Errors

DnsPacket.InvalidInputError

Thrown for malformed input. When decoding: an unknown label type, a name over 255 bytes, a compression pointer that doesn't point backwards (so loops are rejected), or record data that doesn't match its type. When encoding: an empty label or one over 63 bytes, an invalid IP address, a TXT string over 255 bytes, an OPT record whose name isn't the root, and so on.

class InvalidInputError extends Errors.InvalidInputError {
  readonly kind: string;
}
import * as DnsPacket from '@portable/dns-packet';
 
try {
  DnsPacket.encode({
    answers: [{ type: 'A', name: 'example.com', data: '300.0.0.1' }],
  });
} catch (error) {
  if (error instanceof DnsPacket.InvalidInputError) {
    console.log(error.message);
  }
}

DnsPacket.TruncatedError

Thrown by decode when the input ends before the message does, or a length field points past the end. With framing: 'tcp', this also means the whole message hasn't arrived yet.

class TruncatedError extends Errors.TruncatedError {
  readonly kind: string;
}
import * as DnsPacket from '@portable/dns-packet';
 
try {
  DnsPacket.decode(new Uint8Array(4));
} catch (error) {
  if (error instanceof DnsPacket.TruncatedError) {
    console.log('incomplete message');
  }
}

DnsPacket.UnsupportedError

Thrown by encode for an EDNS option that has no data and no built-in encoder. Only CLIENT_SUBNET, TCP_KEEPALIVE, PADDING and KEY_TAG are built in.

class UnsupportedError extends Errors.UnsupportedError {
  readonly kind: string;
}
import * as DnsPacket from '@portable/dns-packet';
 
try {
  //
 
## Types
 
### `DnsPacket.Opcode`
 
A header opcode name. An opcode without a name is written `OPCODE<number>`.
 
```ts
type Opcode =  |  |  |  |  |  | number;

DnsPacket.OptionCode

An EDNS option code name. A code without a name is written OPTION<number>.

type OptionCode =  |  |  |  |  |  |  |  |  |  |  |  |  |  |  | number;

DnsPacket.Rcode

A header response code name. A code without a name is written RCODE<number>.

type Rcode =  |  |  |  |  |  |  |  |  |  |  |  | number;

DnsPacket.RecordClass

A class name. A class without a name is written CLASS<number>, as in RFC 3597.

type RecordClass =  |  |  |  |  |  | number;

DnsPacket.RecordType

A record type name. A type without a name is written TYPE<number>, as in RFC 3597.

type RecordType = keyof typeof typeNumbers | number;

DnsPacket.DecodedOptRecord

An OPT record as decode returns it.

type DecodedOptRecord = \{
    type: ;
    name: ;
    udpPayloadSize: number;
    extendedRcode: number;
    version: number;
    dnssecOk: boolean;
    options: EdnsOption[];
\};

DnsPacket.DecodedPacket

A DNS message as decode returns it, with every field present.

type DecodedPacket = \{
    id: number;
    type:  | ;
    opcode: Opcode;
    rcode: Rcode;
    flags: \{
        [key in keyof Flags]-?: boolean;
    \};
    questions: DecodedQuestion[];
    answers: DecodedResourceRecord[];
    authorities: DecodedResourceRecord[];
    additionals: DecodedResourceRecord[];
\};

DnsPacket.DecodedQuestion

A question as decode returns it.

type DecodedQuestion = \{
  name: string;
  type: RecordType;
  class: RecordClass;
  qu: boolean;
\};

DnsPacket.DecodedResourceRecord

A resource record as decode returns it.

type DecodedResourceRecord =
  | \{
      [key in KnownType]: DecodedFields & \{
        type: key;
        data: DecodedRecordData[key];
      \};
    \}[KnownType]
  | (DecodedFields & \{
      type: UnknownType;
      data: Uint8Array;
    \})
  | DecodedOptRecord;

DnsPacket.Flags

Header flags. Each is one bit of the header, and a missing flag is clear.

type Flags = \{
  aa?: boolean | undefined;
  tc?: boolean | undefined;
  rd?: boolean | undefined;
  ra?: boolean | undefined;
  ad?: boolean | undefined;
  cd?: boolean | undefined;
\};

DnsPacket.OptRecord

The EDNS OPT pseudo-record (RFC 6891). It goes in additionals.

type OptRecord = \{
    type: ;
    name?:  | undefined;
    udpPayloadSize?: number | undefined;
    extendedRcode?: number | undefined;
    version?: number | undefined;
    dnssecOk?: boolean | undefined;
    options?: readonly EdnsOption[] | undefined;
\};

DnsPacket.Packet

A DNS message to encode. Missing fields take their defaults and missing sections are empty.

type Packet = \{
    id?: number | undefined;
    type?:  |  | undefined;
    opcode?: Opcode | undefined;
    rcode?: Rcode | undefined;
    flags?: Flags | undefined;
    questions?: readonly Question[] | undefined;
    answers?: readonly ResourceRecord[] | undefined;
    authorities?: readonly ResourceRecord[] | undefined;
    additionals?: readonly ResourceRecord[] | undefined;
\};

DnsPacket.Question

A question: the name, type and class being looked up.

type Question = \{
  name: string;
  type: RecordType;
  class?: RecordClass | undefined;
  qu?: boolean | undefined;
\};

DnsPacket.ResourceRecord

A resource record in the answer, authority or additional section.

type ResourceRecord =
  | \{
      [key in KnownType]: Fields & \{
        type: key;
        data: RecordData[key];
      \};
    \}[KnownType]
  | (Fields & \{
      type: UnknownType;
      data: Uint8Array;
    \})
  | OptRecord;

DnsPacket.UnknownType

A record type with no codec here. Its data is the raw RDATA.

type UnknownType = Exclude&lt;RecordType, KnownType | >;

DnsPacket.DecodedRecordData

The data of each record type as decode returns it.

type DecodedRecordData = Omit&lt;RecordData> & \{
  TXT: Uint8Array[];
\};

DnsPacket.EdnsOption

An EDNS option inside an OPT record. CLIENT_SUBNET, TCP_KEEPALIVE, PADDING and KEY_TAG have fields. For any other code, data holds the raw bytes.

type EdnsOption = \{
    code: ;
    ip: string;
    sourcePrefixLength: number;
    scopePrefixLength?: number | undefined;
\} | \{
    code: ;
    timeout?: number | undefined;
\} | \{
    code: ;
    length: number;
\} | \{
    code: ;
    tags: readonly number[];
\} | \{
    code: Exclude&lt;OptionCode,  |  |  | >;
    data: Uint8Array;
\};

DnsPacket.RecordData

The data of each record type that has a codec. decode returns the same shapes, except that TXT strings are always Uint8Array.

type RecordData = \{
  A: string;
  AAAA: string;
  CNAME: string;
  DNAME: string;
  NS: string;
  PTR: string;
  MX: \{
    preference: number;
    exchange: string;
  \};
  SOA: \{
    mname: string;
    rname: string;
    serial: number;
    refresh: number;
    retry: number;
    expire: number;
    minimum: number;
  \};
  SRV: \{
    priority: number;
    weight: number;
    port: number;
    target: string;
  \};
  TXT: readonly (string | Uint8Array)[];
  NULL: Uint8Array;
  HINFO: \{
    cpu: string;
    os: string;
  \};
  CAA: \{
    flags: number;
    tag: string;
    value: string;
  \};
  NAPTR: \{
    order: number;
    preference: number;
    flags: string;
    services: string;
    regexp: string;
    replacement: string;
  \};
  TLSA: \{
    usage: number;
    selector: number;
    matchingType: number;
    certificate: Uint8Array;
  \};
  SSHFP: \{
    algorithm: number;
    fingerprintType: number;
    fingerprint: Uint8Array;
  \};
  DS: DigestData;
  CDS: DigestData;
  DNSKEY: KeyData;
  CDNSKEY: KeyData;
  RRSIG: \{
    typeCovered: RecordType;
    algorithm: number;
    labels: number;
    originalTTL: number;
    expiration: number;
    inception: number;
    keyTag: number;
    signersName: string;
    signature: Uint8Array;
  \};
  RP: \{
    mbox: string;
    txt: string;
  \};
  NSEC: \{
    nextDomain: string;
    types: readonly RecordType[];
  \};
  NSEC3: \{
    algorithm: number;
    flags: number;
    iterations: number;
    salt: Uint8Array;
    nextHashedOwner: Uint8Array;
    types: readonly RecordType[];
  \};
\};