resolve

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: May 23, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package resolve is the DNS query path. Given a query and a branch (or a selector match resolving to a branch), it walks the branch's tree and returns the matching RRset.

Package resolve is the DNS query path. Given a query plus the active branch (selected by a Router), it walks the branch's tree and returns the matching RRset.

This package owns:

  • The Resolver type and its dns.HandlerFunc adapter.
  • The Snapshotter contract that hides the per-process repo handle lifecycle (see snapshot.go).
  • CNAME chase, NODATA vs NXDOMAIN classification, SOA-at-apex authority attachment for negative answers.
  • AXFR (full-zone transfer) — see axfr.go.
  • Time-travel: when PinnedAt is non-zero, the resolver serves a frozen historical commit instead of following a branch.

The package is pure of CLI / flag plumbing — that lives in cmd/zonegitd.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	Zone          string     // lowercase FQDN with trailing dot
	DefaultBranch string     // branch served when Router is nil or returns ""
	PinnedAt      store.Hash // non-zero => freeze to this commit (time-travel)
	Router        Router     // optional canary routing
	MetricsHook   MetricsHook
}

Config configures a Resolver. Construct once at startup; the resolver reads it read-only thereafter.

type Metrics

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

Metrics is a tiny, allocation-free Prometheus-format exporter that avoids pulling in github.com/prometheus/client_golang.

Series exposed:

zonegit_dns_queries_total{qtype="A",rcode="NOERROR"} N
zonegit_repo_head_branch  (info gauge, value is always 1, label "branch")

Cardinality is bounded by (qtypes seen) × (rcodes seen) and stays small even on adversarial input because the qtype/rcode strings are drawn from a fixed dns.TypeToString / dns.RcodeToString set.

func NewMetrics

func NewMetrics() *Metrics

NewMetrics returns a Metrics with empty counters.

func (*Metrics) Observe

func (m *Metrics) Observe(qtype string, rcode int)

Observe implements MetricsHook.

func (*Metrics) ServeHTTP

func (m *Metrics) ServeHTTP(w http.ResponseWriter, _ *http.Request)

ServeHTTP renders metrics in Prometheus exposition format.

func (*Metrics) SetActiveBranch

func (m *Metrics) SetActiveBranch(label string)

SetActiveBranch records the branch the daemon is currently serving (or the canary rule summary). Shown as a labeled info gauge so operators can confirm at a glance which branch the process is bound to.

type MetricsHook

type MetricsHook interface {
	Observe(qtype string, rcode int)
}

MetricsHook is called once per handled DNS query, after the response is fully constructed but before it is written to the wire. Implementations must not retain pointers into the message.

type PollingSnapshotter

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

PollingSnapshotter keeps a single read-only Repo open and reopens it only when one of the watched refs changes its hash. This replaces the v0 behaviour of opening Badger per DNS query (which capped throughput at single-digit hundreds of QPS) with a single Open at startup plus occasional reopens — typically zero per second on a quiet repo.

The watcher runs in a background goroutine, polling at PollInterval (default 200ms). On detected change it opens a fresh Repo, then atomically swaps and closes the old one. Snapshot() never blocks on disk I/O.

func NewPollingSnapshotter

func NewPollingSnapshotter(path string, branches []string, pollInterval time.Duration) (*PollingSnapshotter, error)

NewPollingSnapshotter opens an initial read-only handle and starts the watcher. branches must include every branch the resolver might serve (default branch + canary branch, if any).

func (*PollingSnapshotter) Close

func (p *PollingSnapshotter) Close() error

Close stops the watcher and releases the cached handle.

func (*PollingSnapshotter) Snapshot

func (p *PollingSnapshotter) Snapshot() (*repo.Repo, error)

Snapshot returns the current cached Repo. The pointer is safe to use concurrently for reads. Writers (Set/Delete/Commit/Init) will fail because the underlying store is opened read-only.

type QueryContext

type QueryContext struct {
	// ClientIP is the source IP of the query (or the EDNS Client Subnet
	// when present). The bucket-hash router keys off the /24 of this.
	ClientIP string
	// QName is the lowercased FQDN.
	QName string
	// QType is the dig-style mnemonic ("A", "AAAA", ...).
	QType string
	// Now is the wall clock at packet receive time.
	Now time.Time
}

QueryContext is the per-packet input to a Router.

type Resolver

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

Resolver is a stateless adapter: every call to Handle pulls a fresh Snapshot() and answers from it. Concurrent calls are safe.

func New

func New(snap Snapshotter, cfg Config) *Resolver

New builds a Resolver. cfg.Zone must be set; cfg.DefaultBranch defaults to "main".

func (*Resolver) Handle

func (r *Resolver) Handle(w dns.ResponseWriter, req *dns.Msg)

Handle implements dns.HandlerFunc. Register it with dns.HandleFunc(zone, r.Handle).

func (*Resolver) HandleWithRemote

func (r *Resolver) HandleWithRemote(w dns.ResponseWriter, req *dns.Msg)

HandleWithRemote is an adapter that captures the remote address before calling Handle, so the Router sees the real client IP (the bare dns.HandlerFunc shape does not provide it directly).

Daemons register this instead of Handle when they care about routing by client subnet (canary, geo, etc.).

type Router

type Router interface {
	// Route returns the branch name to serve this query from. The default
	// branch should be returned when no rule matches.
	Route(QueryContext) string
}

Router decides which branch to serve a query from. A nil Router means "always serve cfg.Branch" (the v0 behaviour).

Implementations live in pkg/route. The interface is here to avoid a circular import — pkg/route would otherwise need to depend on pkg/resolve for the QueryContext, which would be silly.

type Snapshotter

type Snapshotter interface {
	Snapshot() (*repo.Repo, error)
	Close() error
}

Snapshotter returns a (read-only) *repo.Repo that reflects the latest committed state of an on-disk repo. Implementations may cache and only reopen when the underlying state has actually changed; the contract is that callers can call Snapshot() per query without worrying about cost.

Jump to

Keyboard shortcuts

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