UNPKG

@multiformats/dns

Version:

Resolve DNS queries with browser fallback

111 lines (90 loc) 3.21 kB
/* eslint-env browser */ import { Buffer } from 'buffer' import dnsPacket from 'dns-packet' import PQueue from 'p-queue' import { CustomProgressEvent } from 'progress-events' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { RecordType } from '../index.js' import { getTypes } from '../utils/get-types.js' import { toDNSResponse } from '../utils/to-dns-response.js' import type { DNSResolver } from './index.js' import type { DNSResponse } from '../index.js' /** * Browsers limit concurrent connections per host (~6), we don't want to exhaust * the limit so this value controls how many DNS queries can be in flight at * once. */ export const DEFAULT_QUERY_CONCURRENCY = 4 export interface DNSOverHTTPSOptions { queryConcurrency?: number } function toType (type: RecordType): 'A' | 'AAAA' | 'TXT' | 'CNAME' { if (type === RecordType.A) { return 'A' } if (type === RecordType.AAAA) { return 'AAAA' } if (type === RecordType.TXT) { return 'TXT' } if (type === RecordType.CNAME) { return 'CNAME' } throw new Error('Unsupported DNS record type') } /** * Uses the RFC 1035 'application/dns-message' content-type to resolve DNS * queries. * * This resolver needs more dependencies than the non-standard * DNS-JSON-over-HTTPS resolver so can result in a larger bundle size and * consequently is not preferred for browser use. * * @see https://datatracker.ietf.org/doc/html/rfc1035 * @see https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/dns-wireformat/ * @see https://github.com/curl/curl/wiki/DNS-over-HTTPS#publicly-available-servers * @see https://dnsprivacy.org/public_resolvers/ */ export function dnsOverHttps (url: string, init: DNSOverHTTPSOptions = {}): DNSResolver { const httpQueue = new PQueue({ concurrency: init.queryConcurrency ?? DEFAULT_QUERY_CONCURRENCY }) return async (fqdn, options = {}) => { const types = getTypes(options.types) const dnsQuery = dnsPacket.encode({ type: 'query', id: 0, flags: dnsPacket.RECURSION_DESIRED, questions: types.map(type => ({ type: toType(type), name: fqdn })) }) const searchParams = new URLSearchParams() searchParams.set('dns', uint8ArrayToString(dnsQuery, 'base64url')) options.onProgress?.(new CustomProgressEvent<string>('dns:query', { detail: fqdn })) // query DNS over HTTPS server const response = await httpQueue.add(async () => { const res = await fetch(`${url}?${searchParams}`, { headers: { accept: 'application/dns-message' }, signal: options.signal }) if (res.status !== 200) { throw new Error(`Unexpected HTTP status: ${res.status} - ${res.statusText}`) } const buf = await res.arrayBuffer() const response = toDNSResponse(dnsPacket.decode(Buffer.from(buf))) options.onProgress?.(new CustomProgressEvent<DNSResponse>('dns:response', { detail: response })) return response }, { signal: options.signal }) if (response == null) { throw new Error('No DNS response received') } return response } }