Table of contents
Part of nurago, a collection of independent Go packages for backend services.
import "github.com/tecnickcom/nurago/pkg/dnscache"
Package dnscache provides a local DNS cache that is safe for concurrent use, bounded in size, and uses single-flight request collapsing to avoid duplicate lookups.
It exposes Cache.LookupHost and Cache.DialContext, a drop-in replacement for
an http.Transport DialContext.
Caching
Resolved host names are cached for one cache-wide TTL; the authoritative DNS record TTLs are not consulted. Concurrent callers asking for the same host share a single lookup.
Host names are matched case-insensitively and an equivalent trailing dot (FQDN
form) is ignored, so “Example.com”, “example.com” and “example.com.” share a
single entry. A custom Resolver therefore observes the normalized name. Host
names that are already IP literals bypass the resolver and the cache entirely,
mirroring net.Resolver.LookupHost.
The configured size bounds the number of cached address sets, never the number of
distinct hosts looked up; Cache.Len can exceed it.
Dialing
Cache.DialContext dials the resolved IPs sequentially (not raced) in the
resolver’s preference order, interleaving address families (leading with the
resolver-preferred one) so a dead family is not exhausted before the other is
tried. Family-restricted networks (“tcp4”, “udp6”, …) dial only addresses of the
matching family.
WithDialer configures the dialer, WithDialTimeout bounds each attempt, and
WithAddressRotation spreads connections across a host’s records.
Stale-if-error
WithStaleOnFailure serves the last successfully resolved addresses for a window
measured from the failed refresh, so rarely resolved hosts are protected too.
WithStaleIfError is the RFC 5861 variant, whose window is measured from the
addresses’ original expiry.
When To Use
- A client dials the same hostnames repeatedly and resolver latency is measurable.
- Concurrent lookups of the same name should collapse into one query.
Example
// Create a DNS cache holding up to 128 hosts for 5 minutes each,
// using the default net.Resolver.
cache := dnscache.New(nil, 128, 5*time.Minute)
// Wire the cache into an http.Transport so every request reuses cached
// DNS resolutions instead of querying the resolver again.
client := &http.Client{
Transport: &http.Transport{
DialContext: cache.DialContext,
},
}
_ = client // use the client as usual
fmt.Println(cache.Len())
// Output: 0
Full source is in example_dnscache_test.go. More runnable examples are on pkg.go.dev.
Dependencies
This package reaches no external module: it uses only the Go standard library.