diff --git a/README.md b/README.md index 37f5144..2b3d2dc 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ It respects negative caching and TTL. Security features include: bailiwick check ## Benchmarking -`make bench` runs `scripts/bench.sh` (uses `dnsperf`; optionally compares against `kresd`). The script: +`make bench` runs `scripts/bench.py` (uses `dnsperf`; optionally compares against `kresd`). The script: - builds with `make build-perf` - runs PicoDNS with `-stats` and writes a perf JSON report to `perf/picodns-perf.json` diff --git a/internal/cache/ttl.go b/internal/cache/ttl.go index e649a0e..e28a297 100644 --- a/internal/cache/ttl.go +++ b/internal/cache/ttl.go @@ -155,10 +155,6 @@ func (c *TTL[K, V]) Clear() { c.items = make(map[K]ttlEntry[V]) } -// Permanent is a special TTL value that indicates the entry should never expire. -// Use this for addrCache where addresses don't change. -const Permanent = time.Duration(0) - // PermanentCache is a cache that never expires entries. // It's a thin wrapper around TTL with a permanent TTL. type PermanentCache[K comparable, V any] struct { diff --git a/internal/obs/tracer.go b/internal/obs/tracer.go index 478fe0f..3f34186 100644 --- a/internal/obs/tracer.go +++ b/internal/obs/tracer.go @@ -9,7 +9,6 @@ import ( "math" "math/bits" "os" - "runtime" "sort" "strconv" "strings" @@ -393,11 +392,6 @@ func Enabled() bool { return true } -// GoVersion returns the Go version for build info. -func GoVersion() string { - return runtime.Version() -} - func formatDuration(d time.Duration) string { if d < time.Microsecond { return fmt.Sprintf("%dns", d.Nanoseconds()) diff --git a/internal/obs/tracer_noop.go b/internal/obs/tracer_noop.go index c68bb35..ba22104 100644 --- a/internal/obs/tracer_noop.go +++ b/internal/obs/tracer_noop.go @@ -75,8 +75,3 @@ func (r *Registry) ReportJSON() ([]byte, error) { func Enabled() bool { return false } - -// GoVersion returns empty string for non-perf builds. -func GoVersion() string { - return "" -} diff --git a/internal/pool/bytes.go b/internal/pool/bytes.go index 165c8d0..29bedf2 100644 --- a/internal/pool/bytes.go +++ b/internal/pool/bytes.go @@ -1,3 +1,4 @@ +// Package pool provides reusable byte slice pools for DNS message buffers. package pool import "sync" diff --git a/internal/resolver/cache.go b/internal/resolver/cache.go index 86f3b36..d48d392 100644 --- a/internal/resolver/cache.go +++ b/internal/resolver/cache.go @@ -15,6 +15,23 @@ import ( "picodns/internal/types" ) +// cachedTracers holds performance tracing instrumentation for the Cached resolver. +type cachedTracers struct { + resolveFromCache *obs.FuncTracer + resolve *obs.FuncTracer + resolveFastPath *obs.FuncTracer + resolveParseReq *obs.FuncTracer + resolveInflight *obs.FuncTracer + resolveUpstream *obs.FuncTracer + resolveValidate *obs.FuncTracer + resolveCacheSet *obs.FuncTracer + getCachedWithKey *obs.FuncTracer + acquireInflight *obs.FuncTracer + releaseInflight *obs.FuncTracer + setCache *obs.FuncTracer + maybePrefetch *obs.FuncTracer +} + // Cached wraps a Resolver with a read-through cache, inflight deduplication, // stale-while-revalidate, and optional prefetch. type Cached struct { @@ -31,21 +48,7 @@ type Cached struct { CacheHits atomic.Uint64 CacheMiss atomic.Uint64 - tracers struct { - resolveFromCache *obs.FuncTracer - resolve *obs.FuncTracer - resolveFastPath *obs.FuncTracer - resolveParseReq *obs.FuncTracer - resolveInflight *obs.FuncTracer - resolveUpstream *obs.FuncTracer - resolveValidate *obs.FuncTracer - resolveCacheSet *obs.FuncTracer - getCachedWithKey *obs.FuncTracer - acquireInflight *obs.FuncTracer - releaseInflight *obs.FuncTracer - setCache *obs.FuncTracer - maybePrefetch *obs.FuncTracer - } + tracers cachedTracers } // NewCached creates a Cached resolver that fronts the given upstream with a cache layer. diff --git a/internal/resolver/constants.go b/internal/resolver/constants.go index 729113a..58f50ef 100644 --- a/internal/resolver/constants.go +++ b/internal/resolver/constants.go @@ -87,6 +87,15 @@ const ( // EDNS0 settings ednsUDPSize = 1232 // Common safe UDP size to avoid fragmentation + + // Query concurrency + maxConcurrentQueries = 1024 // Maximum in-flight queries (semaphore size) + + // CNAME resolution + maxCNAMEChainLength = 16 // Maximum CNAME chain depth for TTL computation + + // Referral defaults + defaultReferralTTL = 3600 // Default TTL (seconds) for referral NS records when no TTL is present ) // Resolver errors @@ -95,5 +104,4 @@ var ( ErrNoNameservers = errors.New("recursive resolver: no nameservers found") ErrNoGlueRecords = errors.New("recursive resolver: no glue records for NS") ErrCnameLoop = errors.New("recursive resolver: CNAME loop detected") - ErrNoRootServers = errors.New("recursive resolver: no root servers available") ) diff --git a/internal/resolver/recursive.go b/internal/resolver/recursive.go index 8039453..6ee2b49 100644 --- a/internal/resolver/recursive.go +++ b/internal/resolver/recursive.go @@ -23,6 +23,20 @@ type inflightRecursive struct { err error } +// recursiveTracers holds performance tracing instrumentation for the Recursive resolver. +type recursiveTracers struct { + resolve *obs.FuncTracer + resolveIterative *obs.FuncTracer + iterHopWait *obs.FuncTracer + iterParseMsg *obs.FuncTracer + iterMinimize *obs.FuncTracer + iterReferral *obs.FuncTracer + iterResolveNS *obs.FuncTracer + resolveNSNames *obs.FuncTracer + warmup *obs.FuncTracer + warmupRTT *obs.FuncTracer +} + // Recursive performs full iterative DNS resolution starting from the root servers. // It maintains a connection pool, RTT tracker, NS cache, and delegation cache. type Recursive struct { @@ -40,18 +54,7 @@ type Recursive struct { inflightMu sync.Mutex inflight map[uint64]*inflightRecursive - tracers struct { - resolve *obs.FuncTracer - resolveIterative *obs.FuncTracer - iterHopWait *obs.FuncTracer - iterParseMsg *obs.FuncTracer - iterMinimize *obs.FuncTracer - iterReferral *obs.FuncTracer - iterResolveNS *obs.FuncTracer - resolveNSNames *obs.FuncTracer - warmup *obs.FuncTracer - warmupRTT *obs.FuncTracer - } + tracers recursiveTracers } // NewRecursive creates a new recursive DNS resolver. @@ -63,7 +66,7 @@ func NewRecursive(opts ...Option) *Recursive { logger: slog.Default(), nsCache: cache.NewTTL[string, []string](nil), delegationCache: newDelegationCache(), - querySem: make(chan struct{}, 1024), + querySem: make(chan struct{}, maxConcurrentQueries), inflight: make(map[uint64]*inflightRecursive), } r.nsCache.MaxLen = maxNSCacheEntries @@ -516,7 +519,7 @@ func (r *Recursive) resolveIterative(ctx context.Context, reqHeader dns.Header, if len(respMsg.Authorities) > 0 { childZone := zone - minTTL := uint32(3600) + minTTL := uint32(defaultReferralTTL) for _, auth := range respMsg.Authorities { if auth.Type == dns.TypeNS { authZone := auth.Name diff --git a/internal/resolver/utils.go b/internal/resolver/utils.go index 3de517c..0217e17 100644 --- a/internal/resolver/utils.go +++ b/internal/resolver/utils.go @@ -102,7 +102,7 @@ func servfailFromRequest(req []byte) ([]byte, bool) { hdr.ANCount = 0 hdr.NSCount = 0 hdr.ARCount = 0 - _ = dns.WriteHeader(resp, hdr) + _ = dns.WriteHeader(resp, hdr) // cannot fail: buffer validated above return resp, true } @@ -169,7 +169,7 @@ func cacheTTLForResponse(fullResp []byte, msg dns.Message, q dns.Question) (time current := q.Name seen := make(map[string]struct{}, 4) seen[current] = struct{}{} - for i := 0; i < 16; i++ { + for i := 0; i < maxCNAMEChainLength; i++ { var next string var ttl uint32 found := false diff --git a/internal/server/utils.go b/internal/server/utils.go index 772c3b9..494afe0 100644 --- a/internal/server/utils.go +++ b/internal/server/utils.go @@ -35,7 +35,7 @@ func servfailFromRequestInPlace(req []byte) ([]byte, bool) { hdr.ANCount = 0 hdr.NSCount = 0 hdr.ARCount = 0 - _ = dns.WriteHeader(req, hdr) + _ = dns.WriteHeader(req, hdr) // cannot fail: buffer validated above return req[:qEnd], true }