/** * Cross-runtime host address resolution. * * Resolution goes through a short-lived cache that also collapses concurrent * lookups of the same host into one query. The egress guard * (`security/sandbox/worker-egress-guard.ts`) validates every outbound request * through here, so without that collapsing a single page render issues one * query per module fetch. * * That fan-out is what makes it matter. A page importing one CDN package pulls * dozens of modules from the same host, and the underlying resolvers query the * configured nameservers directly rather than going through the OS resolver * cache that getaddrinfo uses. Resolvers that serialize concurrent queries then * add seconds: a Tailscale MagicDNS resolver measured 46ms for one lookup but * 2061ms for 51 concurrent lookups, against roughly 60ms for public resolvers at * the same concurrency. HTTP_MODULE_FETCH_TIMEOUT_MS gives each fetch attempt * 2_500ms rather than each fetch, so DNS alone exhausted one attempt. Every * attempt then failed the same way and the fetches died with AbortError, until * the render hit its idle deadline. * * Caching resolved addresses does not widen the DNS-rebinding window that * pinning closes: reusing an already validated address set is what pinning * does, and every caller still runs the full egress policy against the returned * addresses. Only the DNS answer is cached, never a policy verdict. Empty and * failed resolutions are never cached, so "this host does not resolve" stays a * live question rather than a sticky one. * * @module platform/compat/dns */ export type DnsAddressRecordType = "A" | "AAAA"; export interface ResolveHostAddressesOptions { recordTypes?: readonly DnsAddressRecordType[]; } /** * How long a successful resolution stays reusable. * * Long enough that one page render resolves a CDN host once, short enough that * a legitimate address change is picked up quickly. */ export declare const HOST_ADDRESS_CACHE_TTL_MS = 30000; /** * Upper bound on cached hosts, so hostnames drawn from request data cannot grow * the cache without limit. */ export declare const HOST_ADDRESS_CACHE_MAX_ENTRIES = 256; export type ResolveHostAddresses = (hostname: string, options?: ResolveHostAddressesOptions) => Promise; export interface HostAddressResolverOptions { /** Underlying resolver. Receives the caller's normalized record types. */ resolve: (hostname: string, options: { recordTypes: readonly DnsAddressRecordType[]; }) => Promise; ttlMs?: number; maxEntries?: number; now?: () => number; } /** * Build a resolver that caches successful answers and shares in-flight lookups. * * Exported so the caching behavior can be tested against an injected resolver * and clock. Runtime callers use `resolveHostAddresses`. */ export declare function createHostAddressResolver(options: HostAddressResolverOptions): ResolveHostAddresses; /** * Loopback answers for a reserved name, or `null` when the name is not one. * * Exported for tests: the runtimes disagreed here (#3785) and the fix has to * be assertable without depending on what the host's resolver happens to * answer, which is the very thing that made the divergence invisible. * * @internal */ export declare function resolveLoopbackAddresses(hostname: string, recordTypes: readonly DnsAddressRecordType[]): string[] | null; /** * A DNS lookup failed because net permission is missing, not because the name * did not resolve. Named so boundary layers that collapse unknown errors into * a generic message (e.g. the worker egress broker) can recognize and forward * the permission diagnosis instead of discarding it. */ export declare class DnsPermissionError extends Error { constructor(message: string, options?: { cause?: unknown; }); } export declare function resolveHostAddresses(hostname: string, options?: ResolveHostAddressesOptions): Promise; /** Drop cached resolutions so one test cannot observe another test's answers. */ export declare function __resetHostAddressCacheForTests(): void; //# sourceMappingURL=dns.d.ts.map