middleware

package
v1.6.4 Latest Latest
Warning

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

Go to latest
Published: Apr 25, 2026 License: MIT Imports: 14 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

This section is empty.

Variables

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.

Functions

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 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 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 Ready added in v1.1.0

func Ready() bool

Ready reports whether Setup has completed.

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 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.).

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
	// 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 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 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 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(resp *dns.Msg, keyCD bool)
}

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).

Directories

Path Synopsis
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 - Simple DNS cache
Package kubernetes - Simple DNS cache
Package reflex detects DNS amplification/reflection attacks.
Package reflex detects DNS amplification/reflection attacks.

Jump to

Keyboard shortcuts

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