tangerine
Version:
Tangerine is the best Node.js drop-in replacement for dns.promises.Resolver using DNS over HTTPS ("DoH") via undici with built-in retries, timeouts, smart server rotation, AbortControllers, and caching support for multiple backends (with TTL and purge sup
691 lines (612 loc) • 13.1 kB
TypeScript
// Type definitions for tangerine
// Project: https://github.com/forwardemail/tangerine
// Definitions by: Forward Email <https://forwardemail.net>
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/member-ordering */
import { Resolver } from 'node:dns/promises';
import type { LookupAddress, LookupOptions } from 'node:dns';
export type TangerineOptions = {
/**
* Timeout in milliseconds for DNS queries.
* @default 5000
*/
timeout?: number;
/**
* Number of retry attempts for DNS queries.
* @default 4
*/
tries?: number;
/**
* DNS-over-HTTPS servers to use.
* @default new Set(['1.1.1.1', '1.0.0.1'])
*/
servers?: Set<string> | string[];
/**
* Request options passed to the HTTP client.
*/
requestOptions?: {
method?: 'GET' | 'POST' | 'get' | 'post';
headers?: Record<string, string>;
};
/**
* Protocol to use for DNS-over-HTTPS requests.
* @default 'https'
*/
protocol?: 'http' | 'https';
/**
* DNS result ordering.
* @default 'verbatim' for Node.js >= 17.0.0, 'ipv4first' otherwise
*/
dnsOrder?: 'verbatim' | 'ipv4first';
/**
* Logger instance for debugging.
* Set to `false` to disable logging.
* @default false
*/
logger?:
| false
| {
info: (...args: unknown[]) => void;
warn: (...args: unknown[]) => void;
error: (...args: unknown[]) => void;
};
/**
* ID generator for DNS packets.
* Can be a number or a function that returns a number (sync or async).
* @default 0
*/
id?: number | (() => number) | (() => Promise<number>);
/**
* Concurrency limit for resolveAny queries.
* @default os.cpus().length
*/
concurrency?: number;
/**
* Default IPv4 address for local binding.
* @default '0.0.0.0'
*/
ipv4?: string;
/**
* Default IPv6 address for local binding.
* @default '::0'
*/
ipv6?: string;
/**
* Port for IPv4 local binding.
*/
ipv4Port?: number;
/**
* Port for IPv6 local binding.
*/
ipv6Port?: number;
/**
* Cache instance for storing DNS results.
* Set to `false` to disable caching.
* @default new Map()
*/
cache?: Map<string, unknown> | false;
/**
* Default TTL in seconds for cached DNS results.
* @default 300
*/
defaultTTLSeconds?: number;
/**
* Maximum TTL in seconds for cached DNS results.
* @default 86400
*/
maxTTLSeconds?: number;
/**
* Function to generate cache arguments.
*/
setCacheArgs?: (
key: string,
result: { expires: number; ttl: number }
) => unknown[];
/**
* Whether to return HTTP errors as DNS errors.
* @default false
*/
returnHTTPErrors?: boolean;
/**
* Whether to rotate servers on errors.
* @default true
*/
smartRotate?: boolean;
/**
* Whether to resolve against all configured servers in parallel and return
* the first successful response.
* @default false
*/
parallelResolution?: boolean;
/**
* Default error message for unsuccessful HTTP responses.
* @default 'Unsuccessful HTTP response'
*/
defaultHTTPErrorMessage?: string;
};
export type DnsRecordType =
| 'A'
| 'A6'
| 'AAAA'
| 'AFSDB'
| 'AMTRELAY'
| 'ANY'
| 'APL'
| 'ATMA'
| 'AVC'
| 'AXFR'
| 'CAA'
| 'CDNSKEY'
| 'CDS'
| 'CERT'
| 'CNAME'
| 'CSYNC'
| 'DHCID'
| 'DLV'
| 'DNAME'
| 'DNSKEY'
| 'DOA'
| 'DS'
| 'EID'
| 'EUI48'
| 'EUI64'
| 'GID'
| 'GPOS'
| 'HINFO'
| 'HIP'
| 'HTTPS'
| 'IPSECKEY'
| 'ISDN'
| 'IXFR'
| 'KEY'
| 'KX'
| 'L32'
| 'L64'
| 'LOC'
| 'LP'
| 'MAILA'
| 'MAILB'
| 'MB'
| 'MD'
| 'MF'
| 'MG'
| 'MINFO'
| 'MR'
| 'MX'
| 'NAPTR'
| 'NID'
| 'NIMLOC'
| 'NINFO'
| 'NS'
| 'NSAP'
| 'NSAP-PTR'
| 'NSEC'
| 'NSEC3'
| 'NSEC3PARAM'
| 'NULL'
| 'NXT'
| 'OPENPGPKEY'
| 'OPT'
| 'PTR'
| 'PX'
| 'RKEY'
| 'RP'
| 'RRSIG'
| 'RT'
| 'Reserved'
| 'SIG'
| 'SINK'
| 'SMIMEA'
| 'SOA'
| 'SPF'
| 'SRV'
| 'SSHFP'
| 'SVCB'
| 'TA'
| 'TALINK'
| 'TKEY'
| 'TLSA'
| 'TSIG'
| 'TXT'
| 'UID'
| 'UINFO'
| 'UNSPEC'
| 'URI'
| 'WKS'
| 'X25'
| 'ZONEMD';
export type MxRecord = {
priority: number;
exchange: string;
type?: 'MX';
};
export type NaptrRecord = {
flags: string;
service: string;
regexp: string;
replacement: string;
order: number;
preference: number;
type?: 'NAPTR';
};
export type SoaRecord = {
nsname: string;
hostmaster: string;
serial: number;
refresh: number;
retry: number;
expire: number;
minttl: number;
type?: 'SOA';
};
export type SrvRecord = {
priority: number;
weight: number;
port: number;
name: string;
type?: 'SRV';
};
export type CaaRecord = {
critical: number;
issue?: string;
issuewild?: string;
iodef?: string;
contactemail?: string;
contactphone?: string;
type?: 'CAA';
};
export type CertRecord = {
certType: number | string;
keyTag: number;
algorithm: number;
certificate: Uint8Array | string;
};
export type TlsaRecord = {
usage: number;
selector: number;
mtype: number;
matchingType: number;
cert: Uint8Array;
certificate: Uint8Array;
};
export type HttpsRecord = {
name: string;
ttl: number;
type: 'HTTPS';
priority: number;
target: string;
params: Record<string, unknown>;
};
export type SvcbRecord = {
name: string;
ttl: number;
type: 'SVCB';
priority: number;
target: string;
params: Record<string, unknown>;
};
export type AnyRecord = {
type: string;
[key: string]: unknown;
};
export type ResolveOptions = {
ttl?: boolean;
/**
* If true, set the EDNS0 DO (DNSSEC OK) flag in the outgoing DoH query
* and return a `{ secure, answers }` object instead of the normal result.
* The `secure` property is true when the upstream resolver has validated
* the response via DNSSEC (i.e. the AD flag is set).
*/
dnssecSecure?: boolean;
/**
* EDNS Client Subnet (ECS) option for geolocation-aware DNS responses.
*/
ecsSubnet?: string;
/**
* If true, bypass and refresh the cached result.
*/
purgeCache?: boolean;
};
export type DnssecResult = {
/** Whether the DNS response was DNSSEC-validated (AD flag set). */
secure: boolean;
/** The DNS answer records from the response. */
answers: Array<{
name: string;
type: string;
ttl: number;
class: string;
flush: boolean;
data: unknown;
}>;
};
export type RecordWithTtl = {
address: string;
ttl: number;
};
export type SpoofPacket = {
id: number;
type: 'response';
flags: number;
flag_qr: boolean;
opcode: string;
flag_aa: boolean;
flag_tc: boolean;
flag_rd: boolean;
flag_ra: boolean;
flag_z: boolean;
flag_ad: boolean;
flag_cd: boolean;
rcode: string;
questions: Array<{ name: string; type: string; class: string }>;
answers: Array<{
name: string;
type: string;
ttl: number;
class: string;
flush: boolean;
data: unknown;
}>;
authorities: unknown[];
additionals: unknown[];
ttl: number;
expires: number;
};
export type RequestFunction = (
url: string,
options: Record<string, unknown>
) => Promise<{
statusCode: number;
headers: Record<string, string>;
body: {
arrayBuffer: () => Promise<ArrayBuffer>;
};
}>;
declare class Tangerine extends Resolver {
/**
* Path to the hosts file.
*/
static HOSTFILE: string;
/**
* Parsed hosts from the hosts file.
*/
static HOSTS: Array<{ ip: string; hosts: string[] }>;
/**
* Set of valid DNS record types.
*/
static TYPES: Set<string>;
/**
* Set of DNS error codes.
*/
static CODES: Set<string>;
/**
* Record types supported by resolveAny.
*/
static ANY_TYPES: string[];
/**
* Record types supported by native DNS.
*/
static NATIVE_TYPES: Set<string>;
/**
* Check if a port number is valid.
*/
static isValidPort(port: number): boolean;
/**
* Get address configuration types based on network interfaces.
*/
static getAddrConfigTypes(): 0 | 4 | 6;
/**
* Get a random integer between min and max (inclusive).
*/
static getRandomInt(min: number, max: number): number;
/**
* Combine multiple errors into a single error.
*/
static combineErrors(errors: Error[]): Error;
/**
* Create a DNS error with the specified code.
*/
static createError(
name: string,
rrtype: string,
code: string,
errno?: number
): Error;
/**
* Options passed to the constructor.
*/
options: TangerineOptions;
/**
* Set of active abort controllers.
*/
abortControllers: Set<AbortController>;
/**
* Create a new Tangerine DNS resolver instance.
* @param options - Configuration options
* @param request - HTTP request function (default: undici.request)
*/
constructor(options?: TangerineOptions, request?: RequestFunction);
/**
* Set local addresses for DNS queries.
*/
setLocalAddress(ipv4?: string, ipv6?: string): void;
/**
* Resolve a hostname to IP addresses.
*/
lookup(
name: string,
options?: LookupOptions
): Promise<LookupAddress | LookupAddress[]>;
/**
* Perform a reverse DNS lookup.
*/
lookupService(
address: string,
port: number,
abortController?: AbortController,
purgeCache?: boolean
): Promise<{ hostname: string; service: string }>;
/**
* Reverse DNS lookup for an IP address.
*/
reverse(
ip: string,
abortController?: AbortController,
purgeCache?: boolean
): Promise<string[]>;
/**
* Resolve IPv4 addresses.
*/
resolve4(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<string[] | RecordWithTtl[]>;
/**
* Resolve IPv6 addresses.
*/
resolve6(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<string[] | RecordWithTtl[]>;
/**
* Resolve CAA records.
*/
resolveCaa(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<CaaRecord[]>;
/**
* Resolve CNAME records.
*/
resolveCname(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<string[]>;
/**
* Resolve MX records.
*/
resolveMx(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<MxRecord[]>;
/**
* Resolve NAPTR records.
*/
resolveNaptr(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<NaptrRecord[]>;
/**
* Resolve NS records.
*/
resolveNs(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<string[]>;
/**
* Resolve PTR records.
*/
resolvePtr(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<string[]>;
/**
* Resolve SOA records.
*/
resolveSoa(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<SoaRecord>;
/**
* Resolve SRV records.
*/
resolveSrv(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<SrvRecord[]>;
/**
* Resolve TXT records.
*/
resolveTxt(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<string[][]>;
/**
* Resolve CERT records.
*/
resolveCert(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<CertRecord[]>;
/**
* Resolve TLSA records.
*/
resolveTlsa(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<TlsaRecord[]>;
/**
* Get the list of DNS servers.
*/
getServers(): string[];
/**
* Cancel all pending DNS queries.
*/
cancel(): void;
/**
* Resolve any record type.
*/
resolveAny(
name: string,
options?: ResolveOptions,
abortController?: AbortController
): Promise<AnyRecord[]>;
/**
* Set the default result order for DNS queries.
*/
setDefaultResultOrder(dnsOrder: 'verbatim' | 'ipv4first'): void;
/**
* Set the DNS servers to use.
*/
setServers(servers: string[]): void;
/**
* Create a spoofed DNS packet for testing.
* @param name - The hostname
* @param rrtype - The record type
* @param answers - Array of answers
* @param json - Whether to return JSON string (default: false)
* @param expires - Expiration time in milliseconds (default: 300000 = 5 minutes)
*/
spoofPacket(
name: string,
rrtype: DnsRecordType,
answers?: unknown[],
json?: boolean,
expires?: number | Date
): SpoofPacket | string;
/**
* Resolve DNS records of a specific type.
* When `options.dnssecSecure` is true, returns `DnssecResult` instead.
*/
resolve(
name: string,
rrtype?: DnsRecordType,
options?: ResolveOptions,
abortController?: AbortController
): Promise<unknown | DnssecResult>;
}
export default Tangerine;
export { Tangerine };