Update types

This commit is contained in:
LittleChest 2026-04-08 23:23:49 +08:00
parent 777f92260e
commit 65b05eb449
4 changed files with 903 additions and 1 deletions

277
dns-packet-tests.ts Normal file
View File

@ -0,0 +1,277 @@
import {
Answer,
decode,
DecodedPacket,
encode,
encodingLength,
Packet,
Question,
RECURSION_DESIRED,
streamDecode,
streamEncode,
} from "dns-packet";
const answer: Answer = {
type: "A",
name: "localhost",
ttl: 3600,
data: "127.0.0.1",
class: "ANY",
flush: true,
};
const question: Question = {
type: "A",
name: "localhost",
class: "IN",
};
const inPacket: Packet = {
additionals: [answer],
authorities: [answer],
answers: [answer],
flags: 0,
id: 0,
questions: [question],
type: "query",
};
const inputBuf = new Uint8Array(0);
const length: number = encodingLength(inPacket);
const out: Uint8Array = encode(inPacket, inputBuf, length - length);
const outPacket: DecodedPacket = decode(out, 0);
const flag_qr: boolean = outPacket.flag_qr;
const flag_aa: boolean = outPacket.flag_aa;
const flag_tc: boolean = outPacket.flag_tc;
const flag_rd: boolean = outPacket.flag_rd;
const flag_ra: boolean = outPacket.flag_ra;
const flag_z: boolean = outPacket.flag_z;
const flag_ad: boolean = outPacket.flag_ad;
const flag_cd: boolean = outPacket.flag_cd;
encode(outPacket);
const records: Answer[] = [
{
type: "A",
name: "localhost",
data: "127.0.0.1",
},
{
type: "AAAA",
name: "localhost",
data: "::1",
},
{
type: "CNAME",
name: "localhost",
data: "example.com",
},
{
type: "DNSKEY",
name: "localhost",
data: {
algorithm: 1,
flags: 257,
key: Buffer.from("test"),
},
},
{
type: "DS",
name: "localhost",
data: {
keyTag: 12345,
algorithm: 8,
digestType: 1,
digest: Buffer.from("test"),
},
},
{
type: "NAPTR",
name: "localhost",
data: {
order: 100,
preference: 10,
flags: "s",
services: "SIP+D2U",
regexp: "!^.*$!sip:customer-service@example.com!",
replacement: "_sip._udp.example.com",
},
},
{
type: "NS",
name: "localhost",
data: "ns1.localhost",
},
{
type: "NSEC",
name: "localhost",
data: {
nextDomain: "a.domain",
rrtypes: ["A", "TXT", "RRSIG"],
},
},
{
type: "NSEC3",
name: "localhost",
data: {
algorithm: 1,
flags: 0,
iterations: 2,
salt: Buffer.from("test"),
nextDomain: Buffer.from("test"), // Hashed per RFC5155
rrtypes: ["A", "TXT", "RRSIG"],
},
},
{
type: "MX",
name: "localhost",
data: {
preference: 10,
exchange: "mx.localhost",
},
},
{
type: "TXT",
name: "localhost",
data: "test",
},
{
type: "TXT",
name: "localhost",
data: Buffer.from("test"),
},
{
type: "TXT",
name: "localhost",
data: ["foo", "bar"],
},
{
type: "SRV",
name: "_imap._tcp.localhost",
data: {
priority: 10,
weight: 60,
port: 5060,
target: "imap.example.com",
},
},
{
type: "SOA",
name: "localhost",
data: {
mname: "localhost",
rname: "hostmaster.localhost",
serial: 2021122101,
},
},
{
type: "CAA",
name: "localhost",
data: {
issuerCritical: false,
tag: "issue",
value: "ca.example.com",
},
},
{
type: "TXT",
name: "version.bind",
class: "CH",
data: "1.2.3",
},
{
type: "OPT",
name: ".",
udpPayloadSize: 65535,
extendedRcode: 255,
ednsVersion: 255,
flags: 65535,
flag_do: true,
options: [
{
code: 8,
type: "CLIENT_SUBNET",
sourcePrefixLength: 0,
scopePrefixLength: 0,
ip: "127.0.0.1",
},
{
code: 8,
ip: "127.0.0.1",
},
{
code: 11,
type: "TCP_KEEPALIVE",
},
{
code: 11,
timeout: 2468,
},
{
code: 12,
length: 13,
},
{
code: 14,
tags: [],
},
{
code: 14,
tags: [256],
},
],
},
{
type: "RP",
name: "localhost",
data: {
mbox: "admin.example.com",
txt: "txt.example.com",
},
},
{
type: "RRSIG",
name: "localhost",
data: {
typeCovered: "A",
algorithm: 8,
labels: 1,
originalTTL: 3600,
expiration: Date.now(),
inception: Date.now(),
keyTag: 12345,
signersName: "a.name",
signature: Buffer.from("test"),
},
},
{
type: "SSHFP",
name: "localhost",
data: {
algorithm: 1,
hash: 1,
fingerprint: "A108C9F834354D5B37AF988141C9294822F5BC00",
},
},
];
encode({ answers: records });
// https://github.com/mafintosh/dns-packet/blob/5aebb85c3221292e994d01b68cadf067e78efabf/examples/tcp.js
function getRandomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
let buf = streamEncode({
type: "query",
id: getRandomInt(1, 65534),
flags: RECURSION_DESIRED,
questions: [
{
type: "A",
name: "google.com",
},
],
});
streamDecode(buf);
buf = buf.slice(2 + streamDecode.bytes);

597
index.d.ts vendored Normal file
View File

@ -0,0 +1,597 @@
/// <reference types="node" />
/**
* The currently defined set of DNS record types.
*/
export type RecordType =
| "A"
| "AAAA"
| "AFSDB"
| "APL"
| "AXFR"
| "CAA"
| "CDNSKEY"
| "CDS"
| "CERT"
| "CNAME"
| "DNAME"
| "DHCID"
| "DLV"
| "DNSKEY"
| "DS"
| "HINFO"
| "HIP"
| "HTTPS"
| "IXFR"
| "IPSECKEY"
| "KEY"
| "KX"
| "LOC"
| "MX"
| "NAPTR"
| "NS"
| "NSEC"
| "NSEC3"
| "NSEC3PARAM"
| "NULL"
| "OPT"
| "PTR"
| "RRSIG"
| "RP"
| "SIG"
| "SOA"
| "SPF"
| "SRV"
| "SSHFP"
| "SVCB"
| "TA"
| "TKEY"
| "TLSA"
| "TSIG"
| "TXT"
| "URI";
export type RecordClass = "IN" | "CS" | "CH" | "HS" | "ANY";
export type OpCode = "QUERY" | "IQUERY" | "STATUS" | "NOTIFY" | "UPDATE" | string;
export type RCode = "NOERROR" | "FORMERR" | "SERVFAIL" | "NXDOMAIN" | "NOTIMP" | "REFUSED" | "YXDOMAIN" | "YXRRSET" | "NXRRSET" | "NOTAUTH" | "NOTZONE" | string;
export interface Question {
type: RecordType;
name: string;
class?: RecordClass | undefined;
qu_bit?: boolean | undefined;
}
// Data interfaces for various record types
export interface CaaData {
issuerCritical?: boolean | undefined;
flags?: number | undefined;
tag: "issue" | "issuewild" | "iodef";
value: string;
}
export interface DnskeyData {
flags: number;
algorithm: number;
key: string | Uint8Array;
}
export interface DsData {
keyTag: number;
algorithm: number;
digestType: number;
digest: string | Uint8Array;
}
export interface HInfoData {
cpu: string;
os: string;
}
export interface MxData {
preference?: number | undefined;
exchange: string;
}
export interface NaptrData {
order: number;
preference: number;
flags: string;
services: string;
regexp: string;
replacement: string;
}
export interface NsecData {
nextDomain: string;
rrtypes: string[];
}
export interface Nsec3Data {
algorithm: number;
flags: number;
iterations: number;
salt: Uint8Array;
nextDomain: Uint8Array;
rrtypes: string[];
}
export interface RpData {
mbox: string;
txt: string;
}
export interface RrsigData {
typeCovered: RecordType | string;
algorithm: number;
labels: number;
originalTTL: number;
expiration: number;
inception: number;
keyTag: number;
signersName: string;
signature: string | Uint8Array;
}
export interface SrvData {
port: number;
target: string;
priority?: number | undefined;
weight?: number | undefined;
}
export interface SoaData {
mname: string;
rname: string;
serial?: number | undefined;
refresh?: number | undefined;
retry?: number | undefined;
expire?: number | undefined;
minimum?: number | undefined;
}
export interface SshfpData {
algorithm: number;
hash: number;
fingerprint: string;
}
export interface TlsaData {
usage: number;
selector: number;
matchingType: number;
certificate: string | Uint8Array;
}
export type TxtData = string | Uint8Array | Array<string | Uint8Array>;
// SVCB/HTTPS Parameter types
export interface SvcParamMandatory {
key: "mandatory" | 0;
value: Array<string | number>;
}
export interface SvcParamAlpn {
key: "alpn" | 1;
value: string | string[];
}
export interface SvcParamNoDefaultAlpn {
key: "no-default-alpn" | 2;
value?: number | undefined;
}
export interface SvcParamPort {
key: "port" | 3;
value: number;
}
export interface SvcParamIpv4Hint {
key: "ipv4hint" | 4;
value: string | string[];
}
export interface SvcParamEchConfig {
key: "echconfig" | 5;
value: string | Uint8Array;
needBase64Decode?: boolean | undefined;
}
export interface SvcParamIpv6Hint {
key: "ipv6hint" | 6;
value: string | string[];
}
export interface SvcParamDohPath {
key: "dohpath" | 7;
value: string;
}
export interface SvcParamOdoh {
key: "odoh" | 32769;
value: string | Uint8Array;
needBase64Decode?: boolean | undefined;
}
export interface SvcParamUnknown {
key: string | number;
value?: unknown | undefined;
data?: Uint8Array | undefined;
}
export type SvcParam =
| SvcParamMandatory
| SvcParamAlpn
| SvcParamNoDefaultAlpn
| SvcParamPort
| SvcParamIpv4Hint
| SvcParamEchConfig
| SvcParamIpv6Hint
| SvcParamDohPath
| SvcParamOdoh
| SvcParamUnknown;
export interface HttpsData {
priority: number;
name: string;
values?: Record<string, SvcParam["value"]> | undefined;
}
export interface SvcbData {
priority: number;
name: string;
values?: Record<string, SvcParam["value"]> | undefined;
}
// Generic answer types
export interface GenericAnswer<T> {
type: T;
name: string;
}
export interface BaseAnswer<T, D> extends GenericAnswer<T> {
ttl?: number | undefined;
class?: RecordClass | undefined;
flush?: boolean | undefined;
data: D;
}
// Answer type groupings
export type StringRecordType = "A" | "AAAA" | "CNAME" | "DNAME" | "NS" | "PTR";
export type OtherRecordType =
| "AFSDB"
| "APL"
| "AXFR"
| "CDNSKEY"
| "CDS"
| "CERT"
| "DHCID"
| "DLV"
| "HIP"
| "IPSECKEY"
| "IXFR"
| "KEY"
| "KX"
| "LOC"
| "NSEC3PARAM"
| "NULL"
| "SIG"
| "TA"
| "TKEY"
| "TSIG"
| "URI";
// Specific answer types
export type StringAnswer = BaseAnswer<StringRecordType, string>;
export type BufferAnswer = BaseAnswer<OtherRecordType, Uint8Array>;
export type CaaAnswer = BaseAnswer<"CAA", CaaData>;
export type DnskeyAnswer = BaseAnswer<"DNSKEY", DnskeyData>;
export type DSAnswer = BaseAnswer<"DS", DsData>;
export type HInfoAnswer = BaseAnswer<"HINFO", HInfoData>;
export type MxAnswer = BaseAnswer<"MX", MxData>;
export type NaptrAnswer = BaseAnswer<"NAPTR", NaptrData>;
export type Nsec3Answer = BaseAnswer<"NSEC3", Nsec3Data>;
export type NsecAnswer = BaseAnswer<"NSEC", NsecData>;
export type RpAnswer = BaseAnswer<"RP", RpData>;
export type RrsigAnswer = BaseAnswer<"RRSIG", RrsigData>;
export type SoaAnswer = BaseAnswer<"SOA", SoaData>;
export type SrvAnswer = BaseAnswer<"SRV", SrvData>;
export type SshfpAnswer = BaseAnswer<"SSHFP", SshfpData>;
export type TlsaAnswer = BaseAnswer<"TLSA", TlsaData>;
export type TxtAnswer = BaseAnswer<"TXT", TxtData>;
export type SvcbAnswer = BaseAnswer<"SVCB", SvcbData>;
export type HttpsAnswer = BaseAnswer<"HTTPS", HttpsData>;
// OPT record
interface OptCodes {
OPTION_0: 0;
LLQ: 1;
UL: 2;
NSID: 3;
OPTION_4: 4;
DAU: 5;
DHU: 6;
N3U: 7;
CLIENT_SUBNET: 8;
EXPIRE: 9;
COOKIE: 10;
TCP_KEEPALIVE: 11;
PADDING: 12;
CHAIN: 13;
KEY_TAG: 14;
DEVICEID: 26946;
OPTION_65535: 65535;
}
type OptCodeType = keyof OptCodes;
type OptCode<K extends OptCodeType> = OptCodes[K];
interface GenericOpt<T extends OptCodeType> {
code: OptCode<T>;
type?: T | undefined;
data?: Uint8Array | undefined;
}
interface ClientSubnetOpt extends GenericOpt<"CLIENT_SUBNET"> {
family?: number | undefined;
sourcePrefixLength?: number | undefined;
scopePrefixLength?: number | undefined;
ip?: string | undefined;
}
interface KeepAliveOpt extends GenericOpt<"TCP_KEEPALIVE"> {
timeout?: number | undefined;
}
interface PaddingOpt extends GenericOpt<"PADDING"> {
length?: number | undefined;
}
interface TagOpt extends GenericOpt<"KEY_TAG"> {
tags: number[];
}
export type PacketOpt = ClientSubnetOpt | KeepAliveOpt | PaddingOpt | TagOpt;
export interface OptAnswer extends GenericAnswer<"OPT"> {
udpPayloadSize: number;
extendedRcode: number;
ednsVersion: number;
flags: number;
/**
* Whether or not the DNS DO bit is set
*/
flag_do: boolean;
options: PacketOpt[];
}
// Complete answer union type
export type Answer =
| StringAnswer
| BufferAnswer
| CaaAnswer
| DnskeyAnswer
| DSAnswer
| HInfoAnswer
| HttpsAnswer
| MxAnswer
| NaptrAnswer
| Nsec3Answer
| NsecAnswer
| OptAnswer
| RpAnswer
| RrsigAnswer
| SoaAnswer
| SrvAnswer
| SshfpAnswer
| SvcbAnswer
| TlsaAnswer
| TxtAnswer;
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},
* {@link DNSSEC_OK}.
*/
flags?: number | undefined;
opcode?: OpCode | number | undefined;
rcode?: RCode | number | undefined;
questions?: Question[] | undefined;
answers?: Answer[] | undefined;
additionals?: Answer[] | undefined;
authorities?: Answer[] | undefined;
}
/**
* Decoded packet with individual flag bits extracted.
*/
export interface DecodedPacket extends Packet {
flag_qr: boolean;
flag_aa: boolean;
flag_tc: boolean;
flag_rd: boolean;
flag_ra: boolean;
flag_z: boolean;
flag_ad: boolean;
flag_cd: boolean;
}
// Constants
export const AUTHORITATIVE_ANSWER: number;
export const TRUNCATED_RESPONSE: number;
export const RECURSION_DESIRED: number;
export const RECURSION_AVAILABLE: number;
export const AUTHENTIC_DATA: number;
export const CHECKING_DISABLED: number;
export const DNSSEC_OK: number;
export const NXDOMAIN: number;
// Main functions
export function encode(packet: Packet, buf?: Uint8Array | ArrayBufferLike, offset?: number): Uint8Array;
export namespace encode {
let bytes: number;
}
export function decode(buf: Uint8Array | ArrayBufferLike, offset?: number): DecodedPacket;
export namespace decode {
let bytes: number;
}
export function encodingLength(packet: Packet): number;
export function streamEncode(packet: Packet): Uint8Array;
export namespace streamEncode {
let bytes: number;
}
export function streamDecode(buf: Uint8Array | ArrayBufferLike, offset?: number): DecodedPacket | null;
export namespace streamDecode {
let bytes: number;
}
// Utility/Helper exports
export const name: Codec<string> & {
encode(
str: string,
buf?: Uint8Array | ArrayBufferLike,
offset?: number,
options?: { mail?: boolean }
): Uint8Array;
decode(buf: Uint8Array | ArrayBufferLike, offset?: number, options?: { mail?: boolean }): string;
encodingLength(name: string): number;
};
export const question: Codec<Question>;
export const answer: Codec<Answer>;
export const svcparam: Codec<SvcParam> & {
keyToNumber(keyName: string): number;
numberToKeyName(number: number): string;
};
export const svcb: Codec<SvcbData>;
export const httpssvc: Codec<HttpsData>;
export const a: Codec<string>;
export const aaaa: Codec<string>;
export const cname: Codec<string>;
export const dname: Codec<string>;
export const ptr: Codec<string>;
export const ns: Codec<string>;
export const mx: Codec<MxData>;
export const srv: Codec<SrvData>;
export const caa: Codec<CaaData> & {
ISSUER_CRITICAL: number;
};
export const txt: Codec<Uint8Array[]> & {
encode(data: TxtData, buf?: Uint8Array, offset?: number): Uint8Array;
};
export const null_: Codec<Uint8Array> & {
encode(data: Uint8Array | string, buf?: Uint8Array, offset?: number): Uint8Array;
encodingLength(data?: Uint8Array | string): number;
};
export const hinfo: Codec<HInfoData>;
export const soa: Codec<SoaData>;
export const naptr: Codec<NaptrData>;
export const dnskey: Codec<DnskeyData> & {
PROTOCOL_DNSSEC: number;
ZONE_KEY: number;
SECURE_ENTRYPOINT: number;
};
export const rrsig: Codec<RrsigData>;
export const rp: Codec<RpData>;
export const nsec: Codec<NsecData>;
export const nsec3: Codec<Nsec3Data>;
export const ds: Codec<DsData>;
export const sshfp: Codec<SshfpData> & {
getFingerprintLengthForHashType(hashType: number): number | undefined;
};
export const tlsa: Codec<TlsaData>;
export const opt: Codec<PacketOpt[]>;
export const option: Codec<PacketOpt>;
export const unknown: Codec<Uint8Array> & {
encode(data: Uint8Array | string, buf?: Uint8Array, offset?: number): Uint8Array;
encodingLength(data: Uint8Array | string): number;
};
// Codec interface for type-safe encode/decode implementations
export interface Codec<T = any> {
encode(data: T, buf?: Uint8Array | ArrayBufferLike, offset?: number): Uint8Array;
decode(buf: Uint8Array | ArrayBufferLike, offset?: number): T;
encodingLength(data: T): number;
}
export function record(type: "A" | "AAAA" | "CNAME" | "DNAME" | "NS" | "PTR"): Codec<string>;
export function record(type: "MX"): Codec<MxData>;
export function record(type: "SRV"): Codec<SrvData>;
export function record(type: "SOA"): Codec<SoaData>;
export function record(type: "TXT"): Codec<Uint8Array[]>;
export function record(type: "CAA"): Codec<CaaData>;
export function record(type: "HINFO"): Codec<HInfoData>;
export function record(type: "NAPTR"): Codec<NaptrData>;
export function record(type: "RP"): Codec<RpData>;
export function record(type: "RRSIG"): Codec<RrsigData>;
export function record(type: "DS"): Codec<DsData>;
export function record(type: "DNSKEY"): Codec<DnskeyData>;
export function record(type: "NSEC"): Codec<NsecData>;
export function record(type: "NSEC3"): Codec<Nsec3Data>;
export function record(type: "SSHFP"): Codec<SshfpData>;
export function record(type: "TLSA"): Codec<TlsaData>;
export function record(type: "SVCB"): Codec<SvcbData>;
export function record(type: "HTTPS"): Codec<HttpsData>;
export function record(type: RecordType): Codec<any>;
export {};

View File

@ -19,14 +19,17 @@
"dependencies": { "dependencies": {
"@leichtgewicht/ip-codec": "^2.0.1" "@leichtgewicht/ip-codec": "^2.0.1"
}, },
"types": "index.d.ts",
"devDependencies": { "devDependencies": {
"@types/node": "*",
"eslint": "^5.14.1", "eslint": "^5.14.1",
"eslint-config-standard": "^12.0.0", "eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.16.0", "eslint-plugin-import": "^2.16.0",
"eslint-plugin-node": "^8.0.1", "eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.0.1", "eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.0", "eslint-plugin-standard": "^4.0.0",
"tape": "^4.10.1" "tape": "^4.10.1",
"typescript": "^6.0.2"
}, },
"keywords": [ "keywords": [
"dns", "dns",
@ -38,6 +41,7 @@
], ],
"files": [ "files": [
"index.js", "index.js",
"index.d.ts",
"types.js", "types.js",
"rcodes.js", "rcodes.js",
"opcodes.js", "opcodes.js",

24
tsconfig.json Normal file
View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"module": "node16",
"lib": [
"es6"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"types": ["node"],
"noEmit": true,
"forceConsistentCasingInFileNames": true,
"ignoreDeprecations": "6.0",
"baseUrl": ".",
"paths": {
"dns-packet": ["./index.d.ts"]
}
},
"files": [
"index.d.ts",
"dns-packet-tests.ts"
]
}