middleware

package
v1.7.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 9, 2026 License: MIT Imports: 23 Imported by: 1

Documentation

Overview

Package middleware provides the DNS query middleware pipeline used by sdns. Middlewares are Constructors that produce Handlers; they register into a Registry, which Setup compiles into an immutable Pipeline. Each incoming DNS query runs through the pipeline via a Chain.

Index

Constants

View Source
const (
	// RecursionWorkEDEText is deliberately stable because it is returned to
	// clients as RFC 8914 Extended DNS Error text in enforce mode.
	RecursionWorkEDEText = "Recursion work budget exceeded"

	// RecursionWorkEDECode deliberately uses RFC 8914's finalized catch-all
	// code. The more specific "Unable to Conform to Policy" registry entry
	// still references an Internet-Draft, so it is not part of this wire
	// contract.
	RecursionWorkEDECode = dns.ExtendedErrorCodeOther

	// DNSSECWorkEDEText and DNSSECWorkEDECode distinguish validation-work
	// exhaustion from network fan-out on the wire.
	DNSSECWorkEDEText = "DNSSEC validation work budget exceeded"
	DNSSECWorkEDECode = dns.ExtendedErrorCodeDNSSECIndeterminate
)

Variables

View Source
var (
	// ErrResolutionAttemptLimit identifies a request-tree retry-guard
	// rejection. It is request-local: callers must not treat it as evidence
	// that the upstream server is unhealthy.
	ErrResolutionAttemptLimit = errors.New("resolution attempt limit exceeded")

	// ErrFailureProbeLimit identifies a follower shed after the bounded
	// re-election of an expired RFC 9520 failure probe. The limit belongs to
	// one request cohort and must never create shared failure-cache state.
	ErrFailureProbeLimit = errors.New("failure probe retry limit exceeded")
)
View Source
var DefaultRegistry = NewRegistry()

DefaultRegistry is the package-level registry used by the top-level Register / RegisterAt / RegisterBefore wrappers. Middleware packages register into it from their init hooks.

View Source
var ErrMaxRecursion = errors.New("queryer: max recursion depth exceeded")

ErrMaxRecursion signals that a Queryer.Query call nested past the recursion bound. Built-in paths (CNAME/DNAME/resolver depth) have their own caps; this is the generic safety net for plugin middleware that dispatches the queryer from inside its own ServeDNS and could otherwise loop forever.

View Source
var ErrNoResponse = errors.New("queryer: no response written")

ErrNoResponse signals that the sub-pipeline ran without any middleware writing a response.

View Source
var ErrRecursionWorkLimit = errors.New("recursion work limit exceeded")

ErrRecursionWorkLimit is the sentinel carried by a RecursionWorkLimitError.

Functions

func BeginResolutionAttempt added in v1.7.4

func BeginResolutionAttempt(ctx context.Context, q dns.Question, endpoint, transport string) error

BeginResolutionAttempt records an attempt in the current request tree. A missing guard is tolerated for low-level callers that intentionally operate outside a middleware request.

func CanonicalResolutionEndpoint added in v1.7.4

func CanonicalResolutionEndpoint(endpoint string) string

CanonicalResolutionEndpoint normalizes endpoint spellings before they are used as attempt-guard or duplicate-suppression identities.

func CheckRecursionWorkLocalLimit added in v1.7.4

func CheckRecursionWorkLocalLimit(ctx context.Context, kind RecursionWorkKind, used uint32) error

CheckRecursionWorkLocalLimit checks a local DNSSEC work limit in the current request tree. used is the number of candidates or signatures already examined, so the first rejected value in enforce mode is exactly the limit.

func DebitRecursionWork added in v1.7.4

func DebitRecursionWork(ctx context.Context, kind RecursionWorkKind) error

DebitRecursionWork debits the current tree when accounting is active.

func FinishRecursionWork added in v1.7.4

func FinishRecursionWork(ctx context.Context)

FinishRecursionWork publishes the current tree exactly once. Normal client pipelines finish at the outer Chain boundary; direct Resolver API callers use this when they had to establish their own standalone ledger.

func HasClientECS added in v1.7.4

func HasClientECS(ctx context.Context) bool

HasClientECS reports whether the original client query carried EDNS Client Subnet, even if an earlier middleware stripped the option from the message.

func IsBestEffortRecursionWork added in v1.7.4

func IsBestEffortRecursionWork(ctx context.Context) bool

IsBestEffortRecursionWork reports whether work debited through ctx belongs to an optional branch.

func IsInternal added in v1.6.4

func IsInternal(ctx context.Context) bool

IsInternal reports whether ctx was tagged by MarkInternal. Intended for plugin middleware that wants a ctx-based internal signal; sdns's own middlewares read the writer flag instead.

func IsRequestLocalResolutionError added in v1.7.4

func IsRequestLocalResolutionError(err error) bool

IsRequestLocalResolutionError reports whether err describes this request tree rather than shared upstream resolution state. Such failures must never be admitted to the RFC 9520 failure cache.

func List

func List() []string

List returns every registered middleware name in insertion order, including disabled ones. Before Setup it returns names from the DefaultRegistry; after Setup it returns the snapshot captured at Build.

func MarkClientECS added in v1.7.4

func MarkClientECS(ctx context.Context) context.Context

MarkClientECS returns a context that records that the original client query carried EDNS Client Subnet. It is idempotent so nested pipelines can preserve the marker without adding another context node.

func MarkInternal added in v1.6.4

func MarkInternal(ctx context.Context) context.Context

MarkInternal returns a derived ctx tagged as originating from an internal sub-pipeline run. Provided as public API for plugin middleware that wants to signal internal-ness without relying on the BufferWriter's Internal() flag (e.g. when a plugin spawns its own internal work without going through Queryer.Query).

sdns's own Queryer.Query does NOT call MarkInternal — the BufferWriter it installs already reports Internal()==true, and every in-tree consumer (cache.ServeDNS dedup guard, cache-hit rate limiter) reads the writer flag. Skipping MarkInternal on the hot path saves one context.valueCtx allocation per internal sub-query.

func MarkRequestLocalFailureResponse added in v1.7.4

func MarkRequestLocalFailureResponse(ctx context.Context, msg *dns.Msg, err error)

MarkRequestLocalFailureResponse attaches exact request-local provenance to a terminal DNS response. Pointer identity prevents an unrelated upstream SERVFAIL later selected by failover from inheriting a losing branch's local attempt rejection or deadline.

func MarkValidatedDenialResponse added in v1.7.4

func MarkValidatedDenialResponse(ctx context.Context, msg *dns.Msg, denial ValidatedDenial)

MarkValidatedDenialResponse attaches explicit, resolver-local validation provenance to an exact NXDOMAIN response. It intentionally does not inspect or trust the response's AD bit.

func MarkValidatedNegativeProofResponse added in v1.7.4

func MarkValidatedNegativeProofResponse(
	ctx context.Context,
	msg *dns.Msg,
	negative ValidatedNegativeProof,
)

MarkValidatedNegativeProofResponse attaches explicit resolver-local validation provenance to an exact NXDOMAIN or terminal NODATA response. The caller-supplied Proof pointer is intentionally ignored: a fresh mark always originates at msg, and only explicit propagation may retain an older terminal proof identity.

func PropagateValidatedDenialResponse added in v1.7.4

func PropagateValidatedDenialResponse(ctx context.Context, from, to *dns.Msg) bool

PropagateValidatedDenialResponse copies exact-response provenance from one NXDOMAIN response identity to another. This is the only supported way for a middleware that replaces a response object to preserve validated denial metadata; ordinary dns.Msg copying does not do so.

func PropagateValidatedNegativeProofResponse added in v1.7.4

func PropagateValidatedNegativeProofResponse(ctx context.Context, from, to *dns.Msg) bool

PropagateValidatedNegativeProofResponse preserves the exact terminal proof when a middleware builds a replacement negative response (for example an outer CNAME/DNAME response). Ordinary dns.Msg copying is deliberately not a trust-propagation operation.

func Ready added in v1.1.0

func Ready() bool

Ready reports whether Setup has completed.

func RecursionWorkEDE added in v1.7.4

func RecursionWorkEDE(ctx context.Context) (uint16, string)

RecursionWorkEDE returns the request tree's latched enforcement EDE. When no local rejection is latched (for example, an upstream returned a generic policy error), it preserves the existing recursion-work fallback.

func RecursionWorkEnforcementError added in v1.7.4

func RecursionWorkEnforcementError(ctx context.Context) error

RecursionWorkEnforcementError returns the local request tree's first policy rejection. Checking this context-owned state, rather than trusting an EDE received on the wire, preserves shadow-mode compatibility with upstreams that legitimately emit the same policy code.

func RecursionWorkErrorEDE added in v1.7.4

func RecursionWorkErrorEDE(ctx context.Context, err error) (uint16, string)

RecursionWorkErrorEDE maps a directly returned recursion-work error before consulting the request tree's latched rejection. Direct Queryer and Exchange errors may carry more specific DNSSEC provenance than the context fallback.

func RecursionWorkRootOwned added in v1.7.4

func RecursionWorkRootOwned(ctx context.Context) bool

RecursionWorkRootOwned reports whether an outer middleware Chain owns the ledger's completion boundary, including when the ledger has not yet been materialized.

func Register

func Register(name string, c Constructor)

Register is a package-level shortcut for DefaultRegistry.Register.

func RegisterAt added in v1.1.0

func RegisterAt(name string, c Constructor, idx int)

RegisterAt is a package-level shortcut for DefaultRegistry.RegisterAt.

func RegisterBefore added in v1.1.0

func RegisterBefore(name string, c Constructor, before string)

RegisterBefore is a package-level shortcut for DefaultRegistry.RegisterBefore.

func RejectRecursionWork added in v1.7.4

func RejectRecursionWork(ctx context.Context, kind RecursionWorkKind) error

RejectRecursionWork records a governor rejection that occurs outside an accepted-work counter. Shadow mode observes it without replacing the original operational error; enforce mode returns a typed policy error.

func RequestLocalFailureForResponse added in v1.7.4

func RequestLocalFailureForResponse(ctx context.Context, msg *dns.Msg) error

RequestLocalFailureForResponse returns the typed request-local error attached to this exact response, if any.

func Reset added in v1.6.3

func Reset()

Reset clears the global Pipeline and DefaultRegistry state. It is intended for tests that need a clean slate between runs; production code should never call it.

func Setup

func Setup(cfg *config.Config)

Setup builds the DefaultRegistry against cfg, loads external plugins, publishes the resulting Pipeline globally, and auto-wires sub-pipeline Queryers and shared Stores into every handler that implements the corresponding *Setter interface. It panics if called more than once without an intervening Reset.

Wiring sequence (after Build):

  1. Build queryerSub by filtering handlers that report ClientOnly()==true.
  2. Build prefetchSub as queryerSub without the cache handler (named "cache") — prefetch must reach the upstream resolver / forwarder instead of returning its own about-to-expire entry.
  3. Construct a PipelineQueryer for each sub-pipeline.
  4. Walk enabled handlers and call SetQueryer / SetPrefetchQueryer / SetStore on anything that implements them, sourcing the Store from whichever handler implements StoreProvider.

This keeps the main package free of wiring logic — every middleware declares its participation in the internal chain (ClientOnly), and every consumer declares what it needs (QueryerSetter, StoreSetter, etc.).

func WithBestEffortRecursionWork added in v1.7.4

func WithBestEffortRecursionWork(ctx context.Context) context.Context

WithBestEffortRecursionWork marks optional work that must stop at the shared cap without turning a successful required branch into a policy failure.

func WithRecursionWork added in v1.7.4

func WithRecursionWork(ctx context.Context, ledger *RecursionWorkLedger) context.Context

WithRecursionWork returns a derived context carrying the exact ledger.

func WithResolutionAttemptGuard added in v1.7.4

func WithResolutionAttemptGuard(ctx context.Context, guard *ResolutionAttemptGuard) context.Context

WithResolutionAttemptGuard pins guard into ctx. Pinning is required for detached work because its originating ResponseMeta can be reset and reused.

func WithResponseMeta added in v1.7.3

func WithResponseMeta(ctx context.Context, m *ResponseMeta) context.Context

WithResponseMeta returns a derived ctx carrying m as the request tree's response metadata sink.

Types

type BufferWriter added in v1.6.4

type BufferWriter struct {
	// contains filtered or unexported fields
}

BufferWriter is the dns.ResponseWriter used inside Queryer.Query. It captures the response in memory and presents itself as a TCP connection so edns.ServeDNS picks the TCP-sized (MaxMsgSize) EDNS buffer rather than truncating internal replies at 512 bytes.

Internal() always reports true. middleware.responseWriter.Reset propagates that through the interface check so the cache middleware's remaining Internal() branches keep behaving correctly during Phase 3 — those branches are replaced with queryer.IsInternal(ctx) in Phase 4.

func (*BufferWriter) Close added in v1.6.4

func (w *BufferWriter) Close() error

Close satisfies dns.ResponseWriter.

func (*BufferWriter) Hijack added in v1.6.4

func (w *BufferWriter) Hijack()

Hijack satisfies dns.ResponseWriter.

func (*BufferWriter) Internal added in v1.6.4

func (w *BufferWriter) Internal() bool

Internal reports this writer as belonging to an internal sub-pipeline run. middleware.responseWriter.Reset propagates it via the interface check.

func (*BufferWriter) LocalAddr added in v1.6.4

func (w *BufferWriter) LocalAddr() net.Addr

LocalAddr satisfies dns.ResponseWriter.

func (*BufferWriter) Msg added in v1.6.4

func (w *BufferWriter) Msg() *dns.Msg

Msg returns the captured response (nil if the sub-pipeline never wrote).

func (*BufferWriter) Proto added in v1.6.4

func (w *BufferWriter) Proto() string

Proto is consulted by edns.ServeDNS to decide the UDP vs TCP EDNS buffer cap. Returning "tcp" keeps internal replies from being truncated at 512 bytes.

func (*BufferWriter) RemoteAddr added in v1.6.4

func (w *BufferWriter) RemoteAddr() net.Addr

RemoteAddr satisfies dns.ResponseWriter.

func (*BufferWriter) TsigStatus added in v1.6.4

func (w *BufferWriter) TsigStatus() error

TsigStatus satisfies dns.ResponseWriter.

func (*BufferWriter) TsigTimersOnly added in v1.6.4

func (w *BufferWriter) TsigTimersOnly(bool)

TsigTimersOnly satisfies dns.ResponseWriter.

func (*BufferWriter) Write added in v1.6.4

func (w *BufferWriter) Write(b []byte) (int, error)

Write unpacks b and captures the parsed message.

func (*BufferWriter) WriteMsg added in v1.6.4

func (w *BufferWriter) WriteMsg(m *dns.Msg) error

WriteMsg captures m as the recorded response.

func (*BufferWriter) Written added in v1.6.4

func (w *BufferWriter) Written() bool

Written reports whether any middleware in the sub-pipeline wrote a response.

type Chain added in v1.1.0

type Chain struct {
	Writer  ResponseWriter
	Request *dns.Msg

	// Meta is the pooled backing storage for the request's
	// ResponseMeta. The first middleware that needs a meta sink and
	// finds none in ctx establishes &Meta via WithResponseMeta;
	// nested pipelines then reuse the ctx pointer rather than their
	// own chain's field.
	Meta ResponseMeta
	// contains filtered or unexported fields
}

Chain carries per-request state through the middleware pipeline. Instances are reused via a sync.Pool: NewChain allocates the fixed pipeline reference, Reset rebinds the per-request writer + message.

func NewChain added in v1.1.0

func NewChain(handlers []Handler) *Chain

NewChain returns a Chain bound to the given handler pipeline. The slice is captured by reference and must not be mutated by the caller after this call.

func (*Chain) Cancel added in v1.1.0

func (ch *Chain) Cancel()

Cancel stops the chain without writing a response. Subsequent Next calls become no-ops.

func (*Chain) CancelWithRcode added in v1.1.7

func (ch *Chain) CancelWithRcode(rcode int, do bool)

CancelWithRcode writes a reply with the given rcode and stops the chain. do controls the DO bit in the response's OPT record.

func (*Chain) Next added in v1.1.0

func (ch *Chain) Next(ctx context.Context)

Next invokes the next handler in the chain. Each handler is responsible for calling Next to continue, or Cancel/CancelWithRcode to stop.

func (*Chain) Reset added in v1.1.0

func (ch *Chain) Reset(w dns.ResponseWriter, r *dns.Msg)

Reset rebinds the chain to a fresh writer + request for pool reuse.

type ClientOnly added in v1.6.4

type ClientOnly interface {
	ClientOnly() bool
}

ClientOnly marks a Handler as serving real client traffic only. Middlewares that implement this method returning true are excluded from the internal sub-pipeline built in Setup — they exist to observe, rate-limit, or authorise external client queries, and either add noise (metrics, dnstap, accesslog) or actively hurt (ratelimit, accesslist, reflex) when an internal sub-query traverses them.

The default for handlers that do NOT implement ClientOnly is "include in the internal sub-pipeline" — safe for anything participating in query resolution (hostsfile, blocklist, cache, failover, resolver, forwarder, etc).

type Constructor added in v1.6.3

type Constructor func(*config.Config) Handler

Constructor builds a Handler from config. A Constructor that returns a typed-nil pointer (e.g. `(*Reflex)(nil)`) signals that the middleware is disabled for this config and is skipped at Build time.

type ContextStore added in v1.7.4

type ContextStore interface {
	Store
	GetWithContext(ctx context.Context, req *dns.Msg) (*dns.Msg, bool)
}

ContextStore is the optional request-tree-aware cache contract. The built-in cache uses ctx to share bounded NSEC3 hash results with required DNSSEC validation and to retain client CD/ECS bypass policy across resolver-private DS and DNSKEY sub-queries. Third-party stores can keep implementing Store.

type CutStore added in v1.7.3

type CutStore interface {
	Store
	SetFromResponseWithCut(resp *dns.Msg, keyCD bool, cutUntil time.Time, cutKey uint64)
}

CutStore is the extended production cache contract. Keeping Store's Phase 1b signature stable avoids forcing plugin/test stores to implement lineage identity before Phase 3 needs it; the built-in cache implements both.

type DNSSECCryptoLimiter added in v1.7.4

type DNSSECCryptoLimiter interface {
	TryAcquire() (release func(), ok bool)
}

DNSSECCryptoLimiter is the narrow shared concurrency seam required by optional cache-side DNSSEC work. TryAcquire is deliberately non-blocking: RFC 8198 synthesis is an optimization, so saturation must fall through to ordinary resolution instead of waiting ahead of required validation.

type DNSSECCryptoLimiterProvider added in v1.7.4

type DNSSECCryptoLimiterProvider interface {
	DNSSECCryptoLimiter() DNSSECCryptoLimiter
}

DNSSECCryptoLimiterProvider is implemented by the resolver that owns the process-wide DNSSEC concurrency gate.

type DNSSECCryptoLimiterSetter added in v1.7.4

type DNSSECCryptoLimiterSetter interface {
	SetDNSSECCryptoLimiter(DNSSECCryptoLimiter)
}

DNSSECCryptoLimiterSetter is implemented by optional DNSSEC work consumers such as the RFC 8198 proof cache. Setup wires the resolver-owned instance before publishing the pipeline.

type Handler added in v1.1.0

type Handler interface {
	// Name returns the middleware name. It must match the name used in
	// Register so Pipeline.Get can resolve the handler back.
	Name() string

	// ServeDNS processes a DNS query. A handler is expected to call
	// ch.Next to continue the chain, or ch.Cancel / ch.CancelWithRcode
	// to stop it.
	ServeDNS(ctx context.Context, ch *Chain)
}

Handler is the middleware interface. Implementations must be safe for concurrent use.

func Get

func Get(name string) Handler

Get returns an enabled handler by name from the global Pipeline. Returns nil before Setup or if the middleware is disabled / unknown.

func Handlers

func Handlers() []Handler

Handlers returns the enabled handlers from the global Pipeline. Returns nil before Setup.

type HandlerFunc added in v1.5.0

type HandlerFunc func(context.Context, *Chain)

HandlerFunc adapts a plain function into a Handler. Handy in tests that want to inject a one-off behaviour without declaring a new type.

func (HandlerFunc) Name added in v1.5.0

func (f HandlerFunc) Name() string

Name returns the fixed label "HandlerFunc".

func (HandlerFunc) ServeDNS added in v1.5.0

func (f HandlerFunc) ServeDNS(ctx context.Context, ch *Chain)

ServeDNS dispatches to f.

type Pipeline added in v1.6.3

type Pipeline struct {
	// contains filtered or unexported fields
}

Pipeline is the compiled, immutable middleware chain produced by Registry.Build. Handler fields are set at construction time and never mutated, so every read is safe without synchronization. chainPool is internally mutable (sync.Pool's contract) but serves the same Pipeline across all callers — pooling *Chain keeps per-internal-query allocations off the hot path for Queryer.

func GlobalPipeline added in v1.6.4

func GlobalPipeline() *Pipeline

GlobalPipeline returns the active pipeline snapshot, or nil before Setup. Startup wiring (queryer construction, api purge hooks) reads this once to derive sub-pipelines and enumerate purgers.

func (*Pipeline) Get added in v1.6.3

func (p *Pipeline) Get(name string) Handler

Get returns the enabled handler with the given name or nil if the middleware is not registered or is disabled for the current config.

func (*Pipeline) Handlers added in v1.6.3

func (p *Pipeline) Handlers() []Handler

Handlers returns the enabled handlers in chain order. The returned slice aliases Pipeline's internal storage; callers must not mutate it.

func (*Pipeline) List added in v1.6.3

func (p *Pipeline) List() []string

List returns every registered middleware name in order, including disabled ones. Useful for diagnostics.

func (*Pipeline) NewChain added in v1.6.4

func (p *Pipeline) NewChain() *Chain

NewChain returns a Chain bound to this pipeline's handlers, pulled from the pipeline's own sync.Pool. Callers that dispatch internal sub-queries (queryer.Queryer) should return the chain via PutChain after use to keep per-sub-query allocations off the hot path. Callers that build a chain for long-lived use don't need to return it.

func (*Pipeline) Purgers added in v1.6.4

func (p *Pipeline) Purgers() []Purger

Purgers returns every enabled handler that implements Purger, in pipeline order. The api purge endpoint iterates this to invalidate both the cache middleware's entries and the resolver handler's nameserver cache.

func (*Pipeline) PutChain added in v1.6.4

func (p *Pipeline) PutChain(ch *Chain)

PutChain returns ch to the pipeline's pool. Safe to call with a chain from any pipeline — the sync.Pool is per-pipeline so cross put/get only causes a pool mismatch (never a correctness bug), but callers should pair PutChain with NewChain from the same Pipeline to keep the pool warm.

func (*Pipeline) SubPipeline added in v1.6.4

func (p *Pipeline) SubPipeline(skip ...string) *Pipeline

SubPipeline returns a new Pipeline containing the same handlers in the same order, minus any whose Name() is listed in skip. Used to build the internal sub-pipeline for queryer.Queryer: client-only guards (metrics, dnstap, accesslist, ratelimit, reflex, accesslog, loop) are dropped so internal sub-queries don't pollute observability or double-count against rate limits, but local-answer middlewares (hostsfile, blocklist, kubernetes, as112), cache, failover, and resolver/forwarder stay.

The returned Pipeline is independent of the receiver — it has its own byName index and handler slice. The names list carries forward the full registered list for diagnostics.

type PrefetchQueryerSetter added in v1.6.4

type PrefetchQueryerSetter interface {
	SetPrefetchQueryer(q Queryer)
}

PrefetchQueryerSetter is implemented by handlers that consume the prefetch sub-pipeline Queryer (today: cache middleware's prefetch worker).

type Purger added in v1.6.4

type Purger interface {
	Purge(q dns.Question)
}

Purger is implemented by handlers that maintain cacheable state which can be invalidated by question. The cache middleware and the resolver handler both implement it — Cache purges its positive / negative entries, the resolver purges its nameserver cache.

The api purge endpoint iterates every Purger in the pipeline instead of synthesising a CHAOS-NULL query and routing it through ServeDNS; that keeps purge as a side-effect operation rather than a pseudo-DNS flow that has to survive every middleware on the way through.

type Queryer added in v1.6.4

type Queryer interface {
	Query(ctx context.Context, req *dns.Msg) (*dns.Msg, error)
}

Queryer answers a client-shaped DNS query through the internal sub-pipeline.

func NewPipelineQueryer added in v1.6.4

func NewPipelineQueryer(sub *Pipeline) Queryer

NewPipelineQueryer returns a Queryer that dispatches requests through sub. sub is expected to be the result of Pipeline.SubPipeline with client-only guards filtered out; this function does not validate its shape.

type QueryerSetter added in v1.6.4

type QueryerSetter interface {
	SetQueryer(q Queryer)
}

QueryerSetter is implemented by handlers that consume the internal-sub-pipeline Queryer (today: cache middleware for CNAME chase, resolver for NS A/AAAA and DNAME target).

type RecursionWorkKind added in v1.7.4

type RecursionWorkKind uint8

RecursionWorkKind identifies a separately bounded kind of recursive work.

const (
	RecursionWorkOutboundQuery RecursionWorkKind = iota
	RecursionWorkInternalQuery
	RecursionWorkDNSKEYCandidate
	RecursionWorkRRsetSignature
	RecursionWorkSignature
	RecursionWorkDSDigest
	RecursionWorkNSEC3Hash
	RecursionWorkConcurrentCrypto
)

type RecursionWorkLedger added in v1.7.4

type RecursionWorkLedger struct {
	// contains filtered or unexported fields
}

func EnsureRecursionWork added in v1.7.4

func EnsureRecursionWork(ctx context.Context, policy RecursionWorkPolicy) (context.Context, *RecursionWorkLedger)

EnsureRecursionWork establishes one heap ledger beside ResponseMeta and pins it into the returned context. The first policy in a request tree wins.

func NewRecursionWorkLedger added in v1.7.4

func NewRecursionWorkLedger(policy RecursionWorkPolicy) *RecursionWorkLedger

NewRecursionWorkLedger constructs a ledger for policy. Callers normally use EnsureRecursionWork so every nested pipeline shares the ResponseMeta-owned instance.

func RecursionWorkFrom added in v1.7.4

func RecursionWorkFrom(ctx context.Context) *RecursionWorkLedger

RecursionWorkFrom returns the request-tree ledger, if one exists. The ResponseMeta fallback supports upstream response-writer contexts that were captured before a downstream resolver pinned the ledger directly.

func (*RecursionWorkLedger) CheckLocal added in v1.7.4

func (l *RecursionWorkLedger) CheckLocal(kind RecursionWorkKind, used uint32) error

CheckLocal checks a per-validation-object limit before the next item is examined. used is the number of items already examined: values below the limit are allowed, while used == limit is the first crossing. Local limits do not add to graph-wide counters.

func (*RecursionWorkLedger) Debit added in v1.7.4

Debit reserves one unit of kind. Shadow mode records crossings but always permits work. Enforce mode never lets an accepted counter exceed its cap and records the rejection as terminal provenance for the request tree. Work is never refunded: failed, cancelled, and losing attempts consumed the resource they started.

func (*RecursionWorkLedger) DebitBestEffort added in v1.7.4

func (l *RecursionWorkLedger) DebitBestEffort(kind RecursionWorkKind) error

DebitBestEffort reserves work for an optional branch. It shares the same counters and returns the same limit error to stop that branch, but a rejected debit does not make an otherwise successful request fail later via EnforcementError. Exhaustion remains visible in the tree snapshot and metrics.

func (*RecursionWorkLedger) EnforcementError added in v1.7.4

func (l *RecursionWorkLedger) EnforcementError() error

EnforcementError returns the first rejected dimension in enforce mode.

func (*RecursionWorkLedger) Reject added in v1.7.4

Reject records a governor failure that has no accepted-work counter, such as timing out while waiting for the resolver-wide crypto semaphore.

func (*RecursionWorkLedger) Retain added in v1.7.4

func (l *RecursionWorkLedger) Retain() (release func(), ok bool)

Retain keeps the request-tree ledger open for asynchronous work that can outlive the client-facing chain. The returned release function is safe to call more than once; callers must call it when that work is complete.

func (*RecursionWorkLedger) Snapshot added in v1.7.4

Snapshot returns the current accepted/observed work totals and crossings.

type RecursionWorkLimitError added in v1.7.4

type RecursionWorkLimitError struct {
	Kind  RecursionWorkKind
	Limit uint32
}

RecursionWorkLimitError reports which dimension rejected more work. Error deliberately remains stable on the wire; Kind and Limit are available to logs and tests without leaking policy details to clients.

func (*RecursionWorkLimitError) EDECode added in v1.7.4

func (e *RecursionWorkLimitError) EDECode() uint16

EDECode maps network-work enforcement to RFC 8914's finalized catch-all code and DNSSEC-work enforcement to DNSSEC Indeterminate.

func (*RecursionWorkLimitError) Error added in v1.7.4

func (e *RecursionWorkLimitError) Error() string

func (*RecursionWorkLimitError) Unwrap added in v1.7.4

func (e *RecursionWorkLimitError) Unwrap() error

type RecursionWorkMode added in v1.7.4

type RecursionWorkMode uint8

RecursionWorkMode controls whether request-tree work limits are disabled, observed without changing behaviour, or enforced.

const (
	RecursionWorkOff RecursionWorkMode = iota
	RecursionWorkShadow
	RecursionWorkEnforce
)

type RecursionWorkPolicy added in v1.7.4

type RecursionWorkPolicy struct {
	Mode                    RecursionWorkMode
	MaxOutboundQueries      uint32
	MaxInternalQueries      uint32
	MaxDNSKEYCandidates     uint32
	MaxRRsetSignatureChecks uint32
	MaxSignatureChecks      uint32
	MaxDSDigests            uint32
	MaxNSEC3Hashes          uint32
	MaxConcurrentCrypto     uint32
}

RecursionWorkPolicy is immutable after a ledger is created.

func MustRecursionWorkPolicyFromConfig added in v1.7.4

func MustRecursionWorkPolicyFromConfig(raw config.RecursionFirewallConfig) RecursionWorkPolicy

MustRecursionWorkPolicyFromConfig normalizes, validates, and translates the operator-facing recursion-firewall configuration. Resolver and pipeline construction both use this seam so programmatic callers cannot silently turn an unknown mode into a different security policy.

func (RecursionWorkPolicy) Enabled added in v1.7.4

func (p RecursionWorkPolicy) Enabled() bool

Enabled reports whether the policy should create and account a ledger.

type RecursionWorkSnapshot added in v1.7.4

type RecursionWorkSnapshot struct {
	Mode                          RecursionWorkMode
	OutboundQueries               uint32
	InternalQueries               uint32
	SignatureChecks               uint32
	DSDigests                     uint32
	NSEC3Hashes                   uint32
	OutboundExhausted             bool
	InternalExhausted             bool
	DNSKEYCandidatesExhausted     bool
	RRsetSignatureChecksExhausted bool
	SignatureChecksExhausted      bool
	DSDigestsExhausted            bool
	NSEC3HashesExhausted          bool
	ConcurrentCryptoExhausted     bool
	MaxOutboundQueries            uint32
	MaxInternalQueries            uint32
	MaxDNSKEYCandidates           uint32
	MaxRRsetSignatureChecks       uint32
	MaxSignatureChecks            uint32
	MaxDSDigests                  uint32
	MaxNSEC3Hashes                uint32
	MaxConcurrentCrypto           uint32
}

RecursionWorkSnapshot is a race-safe point-in-time ledger view.

type Registry added in v1.6.3

type Registry struct {
	// contains filtered or unexported fields
}

Registry collects middleware registrations and builds an immutable Pipeline from them. A Registry is safe for concurrent Register calls, but Build is expected to be called once.

func NewRegistry added in v1.6.3

func NewRegistry() *Registry

NewRegistry returns an empty Registry.

func (*Registry) Build added in v1.6.3

func (r *Registry) Build(cfg *config.Config) *Pipeline

Build runs every Constructor against cfg, skips disabled middlewares (typed-nil), and returns an immutable Pipeline. Constructors run outside the registry lock, so they may do heavy work (open files, spawn goroutines) without starving concurrent List calls.

func (*Registry) List added in v1.6.3

func (r *Registry) List() []string

List returns the registered middleware names in order.

func (*Registry) Register added in v1.6.3

func (r *Registry) Register(name string, c Constructor)

Register appends a middleware to the end of the registry.

func (*Registry) RegisterAt added in v1.6.3

func (r *Registry) RegisterAt(name string, c Constructor, idx int)

RegisterAt inserts a middleware at the given index. Out-of-range index panics.

func (*Registry) RegisterBefore added in v1.6.3

func (r *Registry) RegisterBefore(name string, c Constructor, before string)

RegisterBefore inserts a middleware immediately before the named one. Panics if `before` is not registered.

type ResolutionAttemptGuard added in v1.7.4

type ResolutionAttemptGuard struct {
	// contains filtered or unexported fields
}

ResolutionAttemptGuard owns retry state and exact terminal request-local response provenance for one complete client request tree. A mutex keeps both maps atomic across resolver fan-out.

func EnsureResolutionAttemptGuard added in v1.7.4

func EnsureResolutionAttemptGuard(ctx context.Context) (context.Context, *ResolutionAttemptGuard)

EnsureResolutionAttemptGuard establishes and pins the request tree's retry guard. Unlike recursion-work accounting, this RFC safeguard is always on.

func NewResolutionAttemptGuard added in v1.7.4

func NewResolutionAttemptGuard() *ResolutionAttemptGuard

NewResolutionAttemptGuard returns an empty request-tree retry guard.

func ResolutionAttemptGuardFrom added in v1.7.4

func ResolutionAttemptGuardFrom(ctx context.Context) *ResolutionAttemptGuard

ResolutionAttemptGuardFrom returns the request tree's retry guard.

func (*ResolutionAttemptGuard) Begin added in v1.7.4

func (g *ResolutionAttemptGuard) Begin(q dns.Question, endpoint, transport string) error

Begin records one actual network attempt, rejecting the fourth attempt for an identical RFC 9520 tuple. Call it immediately before other accounting and before dialing so a rejected attempt consumes neither work budget nor wire resources.

type ResolutionAttemptLimitError added in v1.7.4

type ResolutionAttemptLimitError struct {
	Question  dns.Question
	Endpoint  string
	Transport string
}

ResolutionAttemptLimitError records the tuple rejected by the RFC 9520 request-tree attempt guard.

func (*ResolutionAttemptLimitError) Error added in v1.7.4

func (*ResolutionAttemptLimitError) Unwrap added in v1.7.4

func (e *ResolutionAttemptLimitError) Unwrap() error

Unwrap lets callers classify a limit error with errors.Is.

type ResolutionFailureStore added in v1.7.4

type ResolutionFailureStore interface {
	Store
	RecordZoneFailure(q dns.Question, zone string)
	ClearZoneFailure(q dns.Question, zone string)
}

ResolutionFailureStore is the optional RFC 9520 extension implemented by the built-in cache. A resolver records a zone only after every usable authority endpoint for that delegation failed; the cache can then suppress random-QNAME retries below the same failed zone (and the resulting parent/ancestor traffic) until the bounded backoff expires.

This stays separate from Store so plugins and test stores that only need ordinary answer caching do not have to implement failure-state policy.

type ResponseMeta added in v1.7.3

type ResponseMeta struct {
	// contains filtered or unexported fields
}

func ResponseMetaFrom added in v1.7.3

func ResponseMetaFrom(ctx context.Context) *ResponseMeta

ResponseMetaFrom returns the ctx's response metadata sink, or nil when none was established (background/priming work).

func (*ResponseMeta) BoundCut added in v1.7.3

func (m *ResponseMeta) BoundCut(deadline time.Time)

(*ResponseMeta).BoundCut folds a delegation-cut deadline into the meta, keeping the earliest. Nil-safe and zero-ignoring so resolver call sites can invoke it unconditionally.

func (*ResponseMeta) BoundCutFor added in v1.7.3

func (m *ResponseMeta) BoundCutFor(deadline time.Time, key uint64)

BoundCutFor folds a delegation-cut deadline and its cache identity into the response metadata. The identity travels with the winning (earliest) deadline as one immutable atomic value, ready for the optional generation checks described by the Ghost/Phoenix durable design.

func (*ResponseMeta) ContextValue added in v1.7.4

func (m *ResponseMeta) ContextValue(key any) (any, bool)

ContextValue lets the root Chain expose its pooled ResponseMeta through a dedicated lazy server request context without another context.WithValue allocation. Exported WithResponseMeta retains normal derived-context isolation.

func (*ResponseMeta) Cut added in v1.7.3

func (m *ResponseMeta) Cut() (time.Time, uint64)

Cut returns the earliest delegation-cut deadline observed for the request tree and the delegation-cache key that supplied it. A zero deadline means unbounded; in that case the key is also zero.

func (*ResponseMeta) CutKey added in v1.7.3

func (m *ResponseMeta) CutKey() uint64

CutKey returns the delegation-cache key associated with CutUntil. It is meaningful only when CutUntil is non-zero.

func (*ResponseMeta) CutUntil added in v1.7.3

func (m *ResponseMeta) CutUntil() time.Time

CutUntil returns the earliest delegation-cut deadline observed for the request tree. Zero means unbounded.

func (*ResponseMeta) EnsureResolutionAttemptGuard added in v1.7.4

func (m *ResponseMeta) EnsureResolutionAttemptGuard() *ResolutionAttemptGuard

EnsureResolutionAttemptGuard returns the request tree's retry guard, installing one on first use.

func (*ResponseMeta) IsCachedFailureResponse added in v1.7.4

func (m *ResponseMeta) IsCachedFailureResponse(msg *dns.Msg) bool

IsCachedFailureResponse reports whether msg is the exact response currently being emitted from the RFC 9520 failure cache for this request tree.

func (*ResponseMeta) MarkCachedFailureResponse added in v1.7.4

func (m *ResponseMeta) MarkCachedFailureResponse(msg *dns.Msg) func()

MarkCachedFailureResponse marks msg as a response currently being emitted from the RFC 9520 failure cache and returns an idempotent release function. Callers must keep the mark only around the synchronous WriteMsg call.

func (*ResponseMeta) RecursionWork added in v1.7.4

func (m *ResponseMeta) RecursionWork() *RecursionWorkLedger

RecursionWork returns the request tree's ledger, if accounting is active.

func (*ResponseMeta) Reset added in v1.7.3

func (m *ResponseMeta) Reset()

Reset clears request metadata before a pooled Chain is reused. Store(nil) is safe even if the ResponseMeta has previously been used; do not replace the struct by assignment because atomic values must not be copied after first use.

func (*ResponseMeta) ResolutionAttemptGuard added in v1.7.4

func (m *ResponseMeta) ResolutionAttemptGuard() *ResolutionAttemptGuard

ResolutionAttemptGuard returns the request tree's retry guard, if present.

type ResponseWriter added in v1.1.0

type ResponseWriter interface {
	dns.ResponseWriter
	Msg() *dns.Msg
	Rcode() int
	Written() bool
	Reset(dns.ResponseWriter)
	Proto() string
	RemoteIP() net.IP
	Internal() bool
}

ResponseWriter implement of dns.ResponseWriter.

type Store added in v1.6.4

type Store interface {
	Get(req *dns.Msg) (*dns.Msg, bool)
	// SetFromResponse stores resp keyed on (Question[0], keyCD).
	// cutUntil bounds the entry's effective lifetime to the
	// delegation cut that produced it (GHSA-mqfw-f48p-2vc8); zero
	// means unbounded.
	SetFromResponse(resp *dns.Msg, keyCD bool, cutUntil time.Time)
}

Store is the minimum cache facade a resolver sub-query needs. Satisfied by cache.Store; declared here so middleware.Setup can wire it from one handler into another without either importing the cache package.

type StoreProvider added in v1.6.4

type StoreProvider interface {
	Store() Store
}

StoreProvider is implemented by handlers that own a Store which should be shared with other handlers (today: the cache middleware; consumed by the resolver handler).

type StoreSetter added in v1.6.4

type StoreSetter interface {
	SetStore(s Store)
}

StoreSetter is implemented by handlers that consume a Store injected at Setup time (today: the resolver handler).

type ValidatedDenial added in v1.7.4

type ValidatedDenial struct {
	DeniedName string
	Zone       string
	// Proof is the original response whose denial proof the resolver
	// authenticated. Middleware may propagate the provenance to a replacement
	// response, but consumers must derive cached proof material from this
	// source rather than from the replacement's possibly rewritten sections.
	Proof *dns.Msg
}

ValidatedDenial is resolver-authenticated NXDOMAIN provenance. It is carried out of band because neither RcodeNameError nor the AD bit proves that this resolver validated the denial itself.

Values are copied into ResponseMeta and never mutated there. DeniedName is the name proven not to exist; Zone is the signed zone that supplied the proof.

func ValidatedDenialForResponse added in v1.7.4

func ValidatedDenialForResponse(ctx context.Context, msg *dns.Msg) (ValidatedDenial, bool)

ValidatedDenialForResponse returns resolver-local validation provenance for this exact NXDOMAIN response. A copied or independently constructed message never inherits the mark.

type ValidatedNegativeProof added in v1.7.4

type ValidatedNegativeProof struct {
	Subject string
	Zone    string
	Kind    ValidatedNegativeProofKind
	Proof   *dns.Msg
	// Aggressive is true only when the stricter RFC 8198 evaluator
	// classified this exact response with the same RCODE. Exact DNSSEC
	// validation can legitimately succeed while aggressive reuse is forbidden
	// (notably NSEC3 Opt-Out); consumers publishing shared proof state must
	// require this bit in addition to local provenance.
	Aggressive bool
}

ValidatedNegativeProof is resolver-authenticated NXDOMAIN or NODATA provenance. Subject is the exact terminal (or QNAME-minimized) name whose denial semantics were checked; Zone is the validated signer zone. Proof is the original terminal response, never an outer alias response assembled later in the request tree.

The zero/Unknown Kind exists only so the older ValidatedDenial compatibility API can preserve its source contract. RFC 8198 admission requires an explicit NSEC or NSEC3 kind.

func ValidatedNegativeProofForResponse added in v1.7.4

func ValidatedNegativeProofForResponse(
	ctx context.Context,
	msg *dns.Msg,
) (ValidatedNegativeProof, bool)

ValidatedNegativeProofForResponse returns resolver-local validation provenance for this exact NXDOMAIN or NODATA response. AD alone, a message copy, or an independently produced negative response never inherits it.

type ValidatedNegativeProofKind added in v1.7.4

type ValidatedNegativeProofKind uint8

ValidatedNegativeProofKind identifies the denial mechanism the resolver authenticated. Keeping this in the provenance prevents a later cache layer from combining NSEC and NSEC3 records, or choosing a different NSEC3 chain than the one whose semantics were checked.

const (
	ValidatedNegativeProofUnknown ValidatedNegativeProofKind = iota
	ValidatedNegativeProofNSEC
	ValidatedNegativeProofNSEC3
)

Directories

Path Synopsis
Package dns64 implements RFC 6147 DNS64.
Package dns64 implements RFC 6147 DNS64.
Package hostsfile implements a high-performance hosts file resolver with advanced features like wildcard support and automatic reloading
Package hostsfile implements a high-performance hosts file resolver with advanced features like wildcard support and automatic reloading
Package kubernetes - Kubernetes API client
Package kubernetes - Kubernetes API client
Package reflex detects DNS amplification/reflection attacks.
Package reflex detects DNS amplification/reflection attacks.
dnssec
Package dnssec implements pure DNSSEC verification primitives: RRSIG/DS validation, NSEC and NSEC3 denial-of-existence proofs, and the EDE-coded sentinel errors they return.
Package dnssec implements pure DNSSEC verification primitives: RRSIG/DS validation, NSEC and NSEC3 denial-of-existence proofs, and the EDE-coded sentinel errors they return.
Package views serves per-client static answers for configured zones.
Package views serves per-client static answers for configured zones.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL