Open-Source Wikis

/

CoreDNS

/

Systems

/

Plugin system

coredns/coredns

Plugin system

Active contributors: miekg, chrisohaver, johnbelamaric

Purpose

The plugin system is the contract between core and the 60+ plugins shipped under plugin/. It defines what a plugin must implement (Handler), how plugins are chained (Plugin = func(Handler) Handler), how a plugin delegates to the next plugin (NextOrFailure), and how response writes are tracked for metrics and tracing.

This page covers the small plugin/plugin.go file and the registration mechanics. The catalog of plugins lives in Plugins overview.

Directory layout

plugin/
├── plugin.go            # Plugin/Handler interfaces, NextOrFailure, ClientWrite, Namespace
├── plugin_test.go
├── register.go          # plugin.Register: alias for caddy.RegisterPlugin
├── normalize.go         # Name, Zones, SplitHostPort, Namespace helpers
├── normalize_test.go
├── backend.go           # ServiceBackend interface for etcd/k8s/route53/...
├── backend_lookup.go    # Shared backend lookup logic
├── done.go              # done sentinel error
├── log_test.go
└── pkg/                 # Shared utility packages (see Shared packages)

The two interfaces

plugin/plugin.go defines exactly two types every plugin author cares about:

type Plugin func(Handler) Handler

type Handler interface {
    ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error)
    Name() string
}

Plugin is a constructor function. When dnsserver.NewServer folds the chain right-to-left, it calls each Plugin with the next handler so plugins can capture their successor. Plugin code typically returns this constructor from setup:

dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
    f.Next = next
    return f
})

Handler is the runtime interface. ServeDNS returns (rcode, err) where:

  • err != nil → CoreDNS responds SERVFAIL.
  • rcode indicates whether the plugin already wrote to the client. CoreDNS treats SERVFAIL, REFUSED, FORMERR, NOTIMP as "no reply written"; any other rcode means the reply has been written.

HandlerFunc is provided as a convenience for tests and one-off handlers; its Name() returns "handlerfunc".

Chain compilation

graph LR
    P3 -->|next nil| P3End[end]
    P2 -->|next P3| P3
    P1 -->|next P2| P2
    Server -->|ServeDNS| P1

Inside dnsserver.NewServer:

var stack plugin.Handler
for i := len(site.Plugin) - 1; i >= 0; i-- {
    stack = site.Plugin[i](stack)
    site.registerHandler(stack)
}
site.pluginChain = stack

Folding right-to-left means the last plugin in directive order is constructed with next == nil. Any earlier plugin captures the previous result. The variable stack ends up holding the head of the chain.

This loop also has side effects:

  • registerHandler(stack) records the plugin under its Name() so Config.Handler(name) can look it up.
  • The first MetadataCollector (typically plugin/metadata) is bookmarked on the config.
  • The trace plugin is bookmarked so Server.Tracer() can return its tracer.
  • Plugins listed in EnableChaos (chaos, forward, proxy) flip Server.classChaos = true.

Delegating to the next plugin

func NextOrFailure(name string, next Handler, ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error)

This helper is used by every plugin that wants to fall through. It does three things:

  1. If next == nil, returns dns.RcodeServerFailure and Error(name, "no next plugin found").
  2. If a tracer span is active in ctx, starts a child span named after the next plugin so requests can be traced through the chain.
  3. Wraps w in a pluginWriter that records the next plugin's name on the inner PluginTracker (implemented by the cache and metrics writers). When the next plugin actually writes, the metrics plugin attributes the response to it.

Response writer tracking

pluginWriter is private but the PluginTracker interface is exported:

type PluginTracker interface {
    SetPlugin(name string)
    GetPlugin() string
}

The plugin/metrics plugin's recorder implements this interface. As the chain unwinds, each NextOrFailure overwrites the recorded plugin name, so the final value is whichever plugin actually called WriteMsg. That name shows up in the coredns_dns_responses_total metric labels.

pluginWriter overrides every method on dns.ResponseWriter: WriteMsg, Write, LocalAddr, RemoteAddr, Close, TsigStatus, TsigTimersOnly, Hijack. The first two also call tracker.SetPlugin(pw.plugin).

Plugin registration

plugin/register.go is one line:

var Register = caddy.RegisterPlugin

Plugins call plugin.Register("name", setup) from init():

func init() {
    plugin.Register("forward", setup)
}

For the registration to take effect, the plugin must be blank-imported by the binary. The blank import lives in core/plugin/zplugin.go, generated from plugin.cfg. Caddy then drives the directive lifecycle in the order from zdirectives.go.

Helpers in plugin.go

Symbol Purpose
Error(name, err) Wraps err as plugin/<name>: <msg>. Used in setup errors.
ClientWrite(rcode) True if a non-error rcode means the client already received a reply.
ErrOnce Returned from setup when a plugin can only be configured once per server block.
Namespace Prometheus namespace string "coredns".
TimeBuckets / SlimTimeBuckets Standard Prometheus histogram buckets.
NativeHistogramBucketFactor 1.05; default for native histograms.

plugin/normalize.go adds:

Symbol Purpose
Name A type representing a DNS name with Matches(child) for zone-suffix tests.
Zones A slice of zone names; Matches(qname) returns the longest matching zone.
Host Wrapper for host:port strings with normalization helpers.
SplitHostPort Used by dnsContext.InspectServerBlocks to split keys, including CIDR expansion.

Backend plugins

plugin/backend.go and plugin/backend_lookup.go define the ServiceBackend interface and shared lookup logic used by plugins that resolve names from a key/value backend (etcd, kubernetes, route53, azure, clouddns, nomad, k8s_external). Each backend implements Services(state, exact, opt), Reverse, Lookup, IsNameError, etc. and the shared Lookup walks A/AAAA/SRV/PTR types to build a unified response. See Backends.

Key abstractions

Symbol File Description
Plugin plugin/plugin.go Constructor function func(Handler) Handler
Handler plugin/plugin.go Runtime DNS handler interface
HandlerFunc plugin/plugin.go Adapter that turns a func into a Handler
NextOrFailure plugin/plugin.go Delegate to the next plugin with tracing and response tracking
ClientWrite plugin/plugin.go Decide whether the server must synthesize an error reply
pluginWriter / PluginTracker plugin/plugin.go Track which plugin actually wrote the response
Register plugin/register.go Alias for caddy.RegisterPlugin
ServiceBackend plugin/backend.go Common interface for backend-style plugins

Integration points

  • Caddy. Plugin registration is caddy.RegisterPlugin. The directive lifecycle is driven by Caddy's dns server type.
  • Tracing. NextOrFailure reads the active span from ot.SpanFromContext(ctx) and starts a child. The trace plugin installs the tracer on Server.
  • Metrics. pluginWriter.SetPlugin is the bridge to plugin/metrics for response attribution.
  • Backend plugins. plugin/backend_lookup.go provides shared lookup logic; backend plugins satisfy ServiceBackend.

Entry points for modification

  • Adding a new core plugin contract method → extend Handler. Avoid: this would force every plugin to update.
  • New helper for plugin authors → put it in plugin/pkg/<name>/. See Shared packages.
  • Changing how response writes are attributed → pluginWriter.WriteMsg / Write.
  • New mode for plugin.Error (e.g. structured errors) → plugin/plugin.go.

For plugin (not plugin-system) modifications, start in Plugins overview and the relevant category page.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Plugin system – CoreDNS wiki | Factory