Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
4 changes: 0 additions & 4 deletions internal/cache/ttl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 0 additions & 6 deletions internal/obs/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"math"
"math/bits"
"os"
"runtime"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -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())
Expand Down
5 changes: 0 additions & 5 deletions internal/obs/tracer_noop.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
}
1 change: 1 addition & 0 deletions internal/pool/bytes.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package pool provides reusable byte slice pools for DNS message buffers.
package pool

import "sync"
Expand Down
33 changes: 18 additions & 15 deletions internal/resolver/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.
Expand Down
10 changes: 9 additions & 1 deletion internal/resolver/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
)
31 changes: 17 additions & 14 deletions internal/resolver/recursive.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions internal/resolver/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion internal/server/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down