gatewaydns

package module
v0.1.0 Latest Latest
Warning

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

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

README

GatewayDNS Core

Go Reference CI License

GatewayDNS Core is an embeddable DNS engine for Go. It is a library first and a binary second: you import it, wire it into your own program, and it resolves, filters, caches and serves DNS without asking for a config file, a daemon, a system resolver, or a particular operating system. Everything it needs — the wire codec, the cache, the upstream transports, the policy engine, the server — is Go code with no external dependencies and no platform-specific build tags, so the same engine runs in a Linux sidecar, in a macOS menu-bar app, on a Windows service, and inside your test binary.

Features

Capability What it means
UDP and TCP serving RFC 1035 transports, with EDNS(0) payload negotiation and RFC 2181 truncation.
DoH, DoT, UDP and TCP upstreams Every upstream speaks one interface, so the transport is a configuration choice, not an architectural one.
Multi-provider failover Ordered and racing provider groups with health tracking, so one dead resolver does not become an outage.
Caching RFC 2308 negative caching and serve-stale, keyed on the canonical question.
Per-device and per-domain policy Rules scoped to a client identity or a name, with exact, wildcard and regular-expression matching.
Blocklists and allowlists Hosts-file and domain-list ingestion, with allowlists overriding blocks.
Custom records Synthesise answers locally for names you own, including split-horizon overrides.
Structured logging log/slog throughout; no logger is imposed on the embedder.
Metrics Counters and latency histograms exposed through an interface you implement, so no metrics library is forced on you.
Plugin interfaces Providers, filters and record types are extension points, not forks.

Status

Milestones 1–11 of 12 are complete. The engine resolves, caches, filters and serves DNS today, and gatewaydnsd runs it as a server daemon. What remains is milestone 12, the desktop application, which lives in its own repository and does not change anything here.

# Milestone Contents Status
1 Foundation and wire protocol dnsmsg: names, messages, RDATA, EDNS(0), compression, RFC 3597 passthrough. Done
2 Cross-cutting kernel clock, logging, events, metrics, config. Done
3 Cache Positive and negative caching, TTL policy, serve-stale, eviction. Done
4 Upstream transports UDP, TCP, DoT and DoH clients behind one interface. Done
5 Providers and failover Provider registry, health tracking, ordered and racing groups. Done
6 Resolver pipeline Query lifecycle, deduplication, retries, middleware. Done
7 Policy engine Per-device and per-domain rules, wildcards, regex, blocklists, custom records. Done
8 Server UDP and TCP listeners, pre-bound sockets, graceful shutdown. Done
9 Storage Query log and statistics behind an interface, with retention. Done
10 Management API Read-only observability and policy control over HTTP. Done
11 Facade and packaging The gatewaydns facade and the daemon's distribution packages. Done
12 Desktop The desktop application, in its own repository. Planned

Every package is tested with the race detector, has no dependency outside the standard library, and is checked against miekg/dns for wire compatibility by a separate conformance module that never enters this one's dependency graph.

Quick start

The gatewaydns package at the root is the facade: one type, sensible defaults, and no need to assemble a cache, a provider group, a policy engine, a resolver and a server by hand. Everything underneath stays exported, so anything the facade does not expose is still reachable — see What you can use today.

package main

import (
	"log"
	"net"

	"github.com/daboss2003/dns"
)

func main() {
	engine, err := gatewaydns.New(gatewaydns.Options{
		Upstreams: []string{"tls://1.1.1.1", "tls://9.9.9.9"},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer engine.Close()

	if _, err := engine.AddBlocklistFile("/etc/gatewaydns/ads.txt"); err != nil {
		log.Fatal(err)
	}
	if err := engine.Block("doubleclick.net"); err != nil {
		log.Fatal(err)
	}

	// Port 53 is privileged. Bind before dropping privileges, then hand the
	// sockets over: Serve never opens one itself, which is what makes that
	// possible. ListenAndServe is the shortcut for when it does not matter.
	pc, err := net.ListenPacket("udp", ":53")
	if err != nil {
		log.Fatal(err)
	}
	l, err := net.Listen("tcp", ":53")
	if err != nil {
		log.Fatal(err)
	}
	log.Fatal(engine.Serve(pc, l))
}

An engine does not have to listen on anything. engine.Resolve(ctx, query) is the whole resolver as a function call, which is what a program that wants filtered, cached lookups for itself actually needs.

Rules, cache and statistics are live: Block, Allow, AddBlocklist, ClearRules, ClearCache and ClearCacheFor all apply to the next query while the engine is serving, and a rule added after an answer was cached still takes effect — a block that waited out a 24-hour TTL would not be a block.

If you want a server rather than a library, gatewaydnsd is this engine with a configuration file, a management API, a systemd unit and packages for Debian, RPM and Alpine.

What you can use today

dnsmsg is complete and usable on its own. It is a DNS message codec: you give it octets or you give it a Message, and it converts faithfully in both directions.

package main

import (
	"fmt"
	"log"

	"github.com/daboss2003/dns/dnsmsg"
)

func main() {
	// Build a recursive A query, advertising an EDNS(0) payload size.
	var q dnsmsg.Message
	q.SetQuestion(dnsmsg.MustParseName("example.com"), dnsmsg.TypeA, dnsmsg.ClassINET)
	q.ID = 0x1234
	q.SetEDNSDefaults(false)

	wire, err := q.Pack()
	if err != nil {
		log.Fatal(err)
	}

	// Send `wire` over whatever transport you like; you get octets back.
	resp, err := dnsmsg.Unpack(wire)
	if err != nil {
		log.Fatal(err)
	}

	if question, ok := resp.Question(); ok {
		fmt.Println("question:", question)
	}

	for _, rr := range resp.Answers {
		switch rd := rr.Data.(type) {
		case *dnsmsg.A:
			fmt.Println(rr.Name, "A", rd.Addr)
		case *dnsmsg.AAAA:
			fmt.Println(rr.Name, "AAAA", rd.Addr)
		case *dnsmsg.CNAME:
			fmt.Println(rr.Name, "CNAME", rd.Target)
		case *dnsmsg.Unknown:
			// RFC 3597: a type with no decoder is kept byte for byte, so a
			// forwarder relays records it does not understand.
			fmt.Println(rr.Name, rr.Type, rd)
		}
	}
}

Answering is the same machinery in reverse. AppendPack writes into a buffer you own, which is what makes an allocation-free server possible:

name := dnsmsg.MustParseName("example.com")

var reply dnsmsg.Message
reply.SetReply(req)
reply.Answers = append(reply.Answers, dnsmsg.RR{
	Name:  name,
	Type:  dnsmsg.TypeA,
	Class: dnsmsg.ClassINET,
	TTL:   300,
	Data:  &dnsmsg.A{Addr: netip.MustParseAddr("192.0.2.1")},
})
reply.SetEDNSDefaults(false)

// Respect the requestor's advertised payload size. Records that do not fit are
// shed and the TC bit is set, per RFC 2181 section 9.
out, err := reply.AppendPack(buf[:0], &dnsmsg.PackOptions{MaxSize: int(req.UDPSize())})

Both use the API exactly as it ships today — the first as a complete program, the second as the fragment inside a request handler. See the package documentation for the rest: Name parsing and comparison, the Registry extension point for private record types, EDNS options, and the PackOptions and UnpackOptions knobs.

Design principles

Zero dependencies. The module's dependency graph is empty and will stay empty. A DNS engine sits on the network edge and parses hostile input; every dependency is an audit surface someone else controls and a supply-chain risk you inherit. Conformance tests that genuinely need third-party libraries live in the nested ./conformance module, so importers never see them.

Nothing OS-specific. No build tags, no syscall, no assumptions about /etc/resolv.conf, netlink, or the Windows registry. Platform integration is the embedder's job, and the CI cross-compiles for Linux, macOS and Windows on amd64 and arm64 to keep that honest.

Interfaces over implementations. Providers, caches, filters, clocks, metric recorders and event subscribers are interfaces. The default implementations are ordinary consumers of those interfaces with no privileged access, which is the only reliable way to know an extension point actually works. Logging is the one place we deliberately did not declare an interface: log/slog already defines that seam as slog.Handler, and a competing one would cost every consumer an adapter — see ADR 0010.

No global state. No package-level singletons, no init-time side effects that reach outside the package, no hidden registries you cannot replace. Two engines in one process are fully independent, which is what makes tests parallelisable and multi-tenancy possible.

Everything testable in isolation. Every component can be constructed and exercised without a network, without a filesystem, and without a real clock. If a design cannot be tested that way, the design is wrong.

Documentation

Three repositories

This repository is the engine: a library, with no external dependencies and no opinions about its host. Two other repositories consume it exactly as any third party would, with a pinned require on a tagged version.

Repository What it is
gatewaydns (here) The engine. Embed it in a Go program.
gatewaydnsd The server daemon. Install it on a box and point DHCP at it.
gatewaydns-desktop The desktop application: hotspot, DHCP, NAT, firewall, UI.

They are separate because the engine's empty dependency graph is a promise to everyone who embeds it, and a daemon has good reasons to want dependencies a library must never have — a YAML parser so operators get comments in their config files, a Prometheus client so /metrics is the format every monitoring stack already scrapes. Keeping them apart means neither has to argue with the other. See ADR 0015.

Documentation is written alongside the milestone that makes it true, so this map grows as the engine does.

Path Contents
docs/ Architecture, design notes and operational guides.
docs/ARCHITECTURE.md The shape of the system, the layer rules that hold it in that shape, and the threat model.
docs/KERNEL.md The cross-cutting kernel — clock, logging, events, metrics, config — for someone embedding the engine, with compilable examples and the cardinality warning.
docs/adr/ Architecture decision records: what was decided, what was rejected, and why.
dnsmsg/doc.go Package overview for the wire codec: representation choices, allocation behaviour, concurrency and robustness guarantees.
CONTRIBUTING.md Build, test, review and API-stability rules.
SECURITY.md Threat model and vulnerability disclosure.
CHANGELOG.md Release history, in Keep a Changelog format.

Requirements

Go 1.25 or later.

License

Apache License 2.0. See LICENSE.

Apache-2.0 rather than MIT because it grants patent rights explicitly. For a networking library that a company may embed in a shipped product, that explicit grant is what removes the legal review from the adoption decision.

Documentation

Overview

Package gatewaydns is an embeddable DNS engine: a caching, filtering, forwarding resolver you construct inside your own program.

engine, err := gatewaydns.New(gatewaydns.Options{
    Upstreams: []string{"tls://1.1.1.1:853", "tls://9.9.9.9:853"},
})
if err != nil {
    return err
}
defer engine.Close()

if err := engine.AddBlocklistFile("/etc/blocklist.txt"); err != nil {
    return err
}
if err := engine.ListenAndServe("127.0.0.1:5353"); err != nil {
    return err
}

What this package is

It is a facade, and only a facade. Every decision it makes is a default over the packages beneath it, and every one of those packages is usable on its own: github.com/daboss2003/dns/dnsmsg for the wire format, github.com/daboss2003/dns/cache for caching, and so on down the list in the architecture documentation. Nothing here is required to use any of them, and nothing there depends on anything here.

That matters because a facade's defaults are opinions, and opinions age. When one of these stops fitting — a different failover strategy, a policy engine of your own, a transport this project has not implemented — the answer is to assemble the pieces directly rather than to wait for an option to be added. The facade exists to make the common case one call, not to be the only door.

What it will not do for you

It does not open a privileged port on your behalf, drop privileges, daemonise, write a PID file, install a service, or reach for a configuration file it was not given. Those belong to whatever is embedding this, and a library that did them would be making decisions about a process it does not own.

Engine.Serve takes listeners the caller opened, precisely so a program that needs port 53 can bind it while it still has the privilege to do so and hand the sockets over afterwards. Engine.ListenAndServe is the convenience form for everything else.

Concurrency

An Engine is safe for concurrent use once constructed. Blocklists and rules may be replaced while it is serving: the swap is atomic, so a reload of a million entries never blocks a query.

Example

A filtering forwarder on port 53, shut down cleanly on a signal.

The sockets are opened here rather than inside the engine because port 53 is privileged: a process that starts as root binds them, hands them over, and can then drop to an unprivileged user before the first query arrives.

engine, err := gatewaydns.New(gatewaydns.Options{
	Upstreams:  []string{"tls://1.1.1.1", "tls://9.9.9.9"},
	ServeStale: 24 * time.Hour,
})
if err != nil {
	log.Fatal(err)
}
defer engine.Close()

if _, err := engine.AddBlocklistFile("/etc/gatewaydns/blocklist.txt"); err != nil {
	log.Fatal(err)
}

pc, err := net.ListenPacket("udp", ":53")
if err != nil {
	log.Fatal(err)
}
l, err := net.Listen("tcp", ":53")
if err != nil {
	log.Fatal(err)
}

go func() {
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	<-sig
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	// Shutdown stops accepting and lets the queries already in flight
	// finish. Close, which the defer above runs, is the hard stop.
	_ = engine.Shutdown(ctx)
}()

if err := engine.Serve(pc, l); err != nil {
	log.Fatal(err)
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Engine

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

Engine is a caching, filtering, forwarding DNS resolver.

func New

func New(opts Options) (*Engine, error)

New builds an engine from opts. It opens no sockets; see Engine.Serve and Engine.ListenAndServe.

func (*Engine) AddAllowlist

func (e *Engine) AddAllowlist(r io.Reader, source string) (policy.ListStats, error)

AddAllowlist loads an allowlist from r.

func (*Engine) AddAllowlistFile

func (e *Engine) AddAllowlistFile(path string) (policy.ListStats, error)

AddAllowlistFile loads an allowlist and installs it.

An allow rule beats a block rule whatever their specificity, which is what makes a large list usable: a list of a million names cannot be audited, so the way anyone recovers from a false positive is to allow the name here rather than to edit a list somebody else maintains.

func (*Engine) AddBlocklist

func (e *Engine) AddBlocklist(r io.Reader, source string) (policy.ListStats, error)

AddBlocklist loads a blocklist from r. source names it in logs and in the reason a blocked client is given.

func (*Engine) AddBlocklistFile

func (e *Engine) AddBlocklistFile(path string) (policy.ListStats, error)

AddBlocklistFile loads a blocklist and installs it.

The format is detected: a hosts file, one name per line, or the DNS-expressible subset of Adblock syntax. A single unparseable line never fails the load — real lists contain stray markup and half-edited entries, and discarding a million good rules over one bad one is the wrong trade — so the count of rejected lines comes back in the policy.ListStats rather than as an error.

Calling it several times accumulates. Rules take effect on the next query: the swap is atomic, so loading a million entries never blocks one.

func (*Engine) Allow

func (e *Engine) Allow(name string) error

Allow adds one rule allowing name and everything under it, overriding any block.

func (*Engine) AllowExact

func (e *Engine) AllowExact(name string) error

AllowExact adds one rule allowing name and nothing under it.

func (*Engine) Block

func (e *Engine) Block(name string) error

Block adds one rule blocking name and everything under it.

Example

Rules can be added and removed while the engine is serving; every change applies to the next query, including one whose answer is already cached.

engine, err := gatewaydns.New(gatewaydns.Options{Upstreams: []string{"1.1.1.1"}})
if err != nil {
	log.Fatal(err)
}
defer engine.Close()

// A name and everything under it.
_ = engine.Block("doubleclick.net")
// That name only, leaving its subdomains alone.
_ = engine.BlockExact("example.com")
// A pattern, held to RE2 so it cannot backtrack into a stall.
_ = engine.BlockRegex(`^ads?[0-9]*\.`)
// An exception, which beats every block above it.
_ = engine.Allow("cdn.doubleclick.net")

fmt.Println(engine.Stats().BlockRules)
Output:
3

func (*Engine) BlockExact

func (e *Engine) BlockExact(name string) error

BlockExact adds one rule blocking name and nothing under it.

func (*Engine) BlockRegex

func (e *Engine) BlockRegex(pattern string) error

BlockRegex adds one rule blocking names matching a regular expression.

Patterns are matched against the canonical (lower-cased) name and are case-insensitive. They are checked only after every literal rule has failed to match, because a literal rule is cheaper and easier to reason about — and they are safe to accept at all only because Go's regexp is RE2: linear in the input with no backtracking, so a pathological pattern costs more than a simple one but cannot be made to cost unboundedly more.

func (*Engine) ClearCache

func (e *Engine) ClearCache()

ClearCache empties the cache.

func (*Engine) ClearCacheFor

func (e *Engine) ClearCacheFor(name string) (int, error)

ClearCacheFor drops name and everything under it, reporting how many entries went.

It is the instrument to reach for when one name's records have changed: flushing everything to fix one name throws away every other answer the resolver has learned, which on a busy network is a self-inflicted latency spike.

func (*Engine) ClearRules

func (e *Engine) ClearRules()

ClearRules discards every rule, leaving the engine resolving everything.

func (*Engine) Close

func (e *Engine) Close() error

Close shuts the engine down and releases everything it opened. It is idempotent.

func (*Engine) Events

func (e *Engine) Events() *events.Bus

Events returns the engine's event bus, on which a caller can subscribe to queries, blocks, upstream failures and configuration reloads.

Events are not logs and not metrics: they are the stream a user interface tails and an integration reacts to. Publication is non-blocking and bounded, so a subscriber that stops reading loses events rather than adding latency to the queries it is watching.

func (*Engine) Handler

func (e *Engine) Handler() resolver.Handler

Handler returns the resolver, for a caller that has an identity to supply.

Engine.Resolve is the simple form and attributes the query to nobody. This is the form a front end uses when it knows which client is asking, so that Options.Identify and Options.Devices can do their work — and so the query is logged as coming from a device rather than from the process itself.

func (*Engine) ListenAndServe

func (e *Engine) ListenAndServe(addr string) error

ListenAndServe opens a UDP socket and a TCP listener on addr and serves both.

func (*Engine) QueryLog

func (e *Engine) QueryLog() storage.Store

QueryLog returns the store the engine writes to, or nil when none was configured. It is how an embedder reads back what was resolved.

func (*Engine) Resolve

func (e *Engine) Resolve(ctx context.Context, q *dnsmsg.Message) (*dnsmsg.Message, error)

Resolve answers one query directly, without a socket.

It is what an embedder uses when the queries are already in hand — a DoH front end, a test, a program resolving on its own behalf — and it is the same path the listeners take, so it exercises the cache, the policy and the upstreams identically. It answers on behalf of nobody in particular, so per-device policy does not apply to it. A caller that knows whose query it is — a DoH front end holding a client address, a test — should use Engine.Handler and pass the client.

Example

An engine can resolve without listening on anything, which is what a program embedding one for its own lookups wants.

engine, err := gatewaydns.New(gatewaydns.Options{
	Upstreams: []string{"https://dns.quad9.net/dns-query"},
})
if err != nil {
	log.Fatal(err)
}
defer engine.Close()

q := new(dnsmsg.Message)
q.SetQuestion(dnsmsg.MustParseName("example.com."), dnsmsg.TypeA, dnsmsg.ClassINET)

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
reply, err := engine.Resolve(ctx, q)
if err != nil {
	log.Fatal(err)
}
for _, rr := range reply.Answers {
	if a, ok := rr.Data.(*dnsmsg.A); ok {
		fmt.Println(a.Addr)
	}
}

func (*Engine) Serve

func (e *Engine) Serve(pc net.PacketConn, l net.Listener) error

Serve answers queries on the given sockets until Engine.Close. Either may be nil, and the sockets remain the caller's to close.

This is the primary entry point rather than Engine.ListenAndServe because port 53 is privileged: a program that needs it binds the socket while it still has the privilege and hands it over here.

func (*Engine) Shutdown

func (e *Engine) Shutdown(ctx context.Context) error

Shutdown stops accepting and waits for queries already accepted to finish.

func (*Engine) Stats

func (e *Engine) Stats() Stats

Stats returns a snapshot.

type Options

type Options struct {
	// Upstreams are endpoints in preference order, each "scheme://address"
	// where scheme is udp, tcp, tls or https. A bare address is treated as udp.
	//
	// A udp:// upstream is automatically paired with tcp:// to the same
	// address, because a truncated UDP answer is an instruction to ask again
	// over a stream and a resolver that could not would fail every large answer.
	Upstreams []string

	// Strategy picks between upstreams. Nil selects sequential failover, which
	// is the right default: it is deterministic, it costs one query, and it
	// respects the order the operator wrote them in. [providers.Race] is faster
	// at the tail and leaks every name to every provider raced, which is a
	// privacy decision rather than a tuning one.
	Strategy providers.Strategy

	// Randomize enables DNS-0x20 case randomisation against upstreams. It costs
	// nothing and adds roughly a bit of entropy per letter against off-path
	// forgery, and is off by default only because a small number of servers
	// normalise case in replies and break outright when it is on.
	Randomize bool

	// CacheEntries bounds the cache. Zero selects a default; negative disables
	// caching entirely, which is a supported configuration for an engine
	// sitting in front of another resolver.
	CacheEntries int

	// MinTTL and MaxTTL clamp how long answers are held.
	MinTTL, MaxTTL time.Duration

	// ServeStale answers from expired entries for this long when no upstream
	// can be reached (RFC 8767). Zero disables it. It is a deliberate
	// correctness trade and so is off unless asked for.
	ServeStale time.Duration

	// BlockMode is what a filtered client is told. See [policy.BlockMode]; none
	// of the choices is simply correct.
	BlockMode policy.BlockMode

	// QueryTimeout bounds one resolution end to end.
	QueryTimeout time.Duration

	// Timeout bounds one upstream exchange.
	Timeout time.Duration

	// Dialer opens upstream connections. Nil uses the standard library's.
	Dialer transport.Dialer

	// QueryLog records resolved queries. Nil keeps no record, which is the most
	// private configuration available and costs nothing to choose.
	QueryLog storage.Store

	// Middleware wraps resolution, outermost first, and runs BEFORE the
	// engine's own policy middleware.
	//
	// Before, because that ordering is what makes the seam useful. The thing a
	// consumer most often needs here is to identify the client — to turn the
	// address a query arrived from into a device, and put it in
	// [resolver.Client.ID] — and policy scoped to a device has to run after
	// something has decided which device it is. A middleware that ran
	// afterwards could observe the decision but not inform it.
	//
	// [resolver.Client] is passed by value, so a middleware sets a field on its
	// own copy and hands that to the next handler; nothing it does can reach
	// back into the server.
	Middleware []resolver.Middleware

	// Devices maps a client to the rules governing it, which is how per-device
	// policy is applied. Nil gives every client the same rules, which is the
	// default.
	//
	// It is called on every query and must be cheap and safe for concurrent
	// use. Pair it with [Options.Identify]: that assigns the identity, this
	// decides what the identity means.
	Devices policy.Resolver

	// Identify turns the address a query arrived from into a device identity,
	// before anything else runs. Nil leaves every query anonymous, which is the
	// default and the most private configuration.
	//
	// It is separate from [Options.Middleware] because it must inform the
	// metrics and the query log as well as the policy decision, and a
	// middleware can only inform what runs after it. See
	// [resolver.Options.Identify].
	Identify func(resolver.Client) resolver.Client

	Clock  clock.Clock
	Logger *slog.Logger
}

Options configure an Engine. The zero value is not usable; at least one upstream is required.

type Stats

type Stats struct {
	Queries metrics.Snapshot `json:"queries"`
	Cache   *cache.Stats     `json:"cache,omitempty"`
	Server  server.Stats     `json:"server"`
	// Rules is how many block and allow rules are installed.
	BlockRules int `json:"block_rules"`
	AllowRules int `json:"allow_rules"`
	// Evaluated and Blocked count policy decisions.
	Evaluated uint64 `json:"evaluated"`
	Blocked   uint64 `json:"blocked"`
}

Stats is a snapshot of what the engine has done.

Directories

Path Synopsis
Package api exposes a resolver's statistics and controls over HTTP.
Package api exposes a resolver's statistics and controls over HTTP.
Package cache stores DNS responses and serves them until they expire.
Package cache stores DNS responses and serves them until they expire.
Package clock abstracts the passage of time so that code depending on it can be tested without waiting.
Package clock abstracts the passage of time so that code depending on it can be tested without waiting.
Package config composes the configuration of a GatewayDNS engine into one document, loads it, validates it, and hands it out to a running server.
Package config composes the configuration of a GatewayDNS engine into one document, loads it, validates it, and hands it out to a running server.
Package dnsmsg implements the DNS message format defined by RFC 1035 and its many successors.
Package dnsmsg implements the DNS message format defined by RFC 1035 and its many successors.
Package events delivers individual occurrences inside the engine to programmatic subscribers, without ever blocking the code that reports them.
Package events delivers individual occurrences inside the engine to programmatic subscribers, without ever blocking the code that reports them.
examples
customtype command
Command customtype implements a resource record type that dnsmsg has never heard of, and round-trips it through the wire codec.
Command customtype implements a resource record type that dnsmsg has never heard of, and round-trips it through the wire codec.
inspect command
Command inspect decodes a DNS message and prints everything in it.
Command inspect decodes a DNS message and prints everything in it.
query command
Command query resolves a single name against a recursive resolver over UDP and prints the response.
Command query resolves a single name against a recursive resolver over UDP and prints the response.
Package logging supplies the parts of structured logging that the standard library leaves to the application, and nothing else.
Package logging supplies the parts of structured logging that the standard library leaves to the application, and nothing else.
Package metrics aggregates what a DNS engine did, so that an operator can answer "is it healthy?" without reading a log.
Package metrics aggregates what a DNS engine did, so that an operator can answer "is it healthy?" without reading a log.
Package policy decides what to do with a query before it is resolved.
Package policy decides what to do with a query before it is resolved.
Package providers turns a set of upstream resolvers into one upstream that keeps working when some of them do not.
Package providers turns a set of upstream resolvers into one upstream that keeps working when some of them do not.
Package resolver answers a DNS query using a cache and a set of upstreams.
Package resolver answers a DNS query using a cache and a set of upstreams.
Package server accepts DNS queries from the network and answers them.
Package server accepts DNS queries from the network and answers them.
Package storage persists what a resolver learns: the query log, per-device statistics, and whatever else a deployment wants to keep across a restart.
Package storage persists what a resolver learns: the query log, per-device statistics, and whatever else a deployment wants to keep across a restart.
Package transport carries a DNS query to an upstream resolver and brings back the reply.
Package transport carries a DNS query to an upstream resolver and brings back the reply.

Jump to

Keyboard shortcuts

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