Writing CoreDNS Middleware
If it is not for me, give it to the next one.
Writing CoreDNS middleware consists out of four parts:
- The actual middleware; the
ServeDNS
method that gets the request. - The setup part, the gets the Corefile configuration and creates the middleware.
- Documentation.
- Registration.
Note that part 1 and 2 also need tests!
Middleware⌗
Let’s take a look at the chaos middleware that returns author and version information
in the CH class. The main entry point for the whole thing is the Chaos
structure. That structure
holds some information and most importantly the Next
middleware.Handler for chaining it to the
next middleware:
type Chaos struct {
Next middleware.Handler
Version string
Authors map[string]bool // get randomization for free \o/
}
The middleware needs to implement the middleware.Handler
interface. That means implementing the
ServerDNS
method:
func (c Chaos) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := middleware.State{W: w, Req: r}
if state.QClass() != dns.ClassCHAOS ||
state.QType() != dns.TypeTXT {
return c.Next.ServeDNS(ctx, w, r)
}
/* do the chaos thing */
Here we create a state
structure that provides use with some handy helper functions, see the
section helper functions below.
Next we check if we should handle this request, and, if not, call the next middleware to handle it.
In chaos_test.go
we test the middleware using ResponseRecorder what allows us to inspect the
message sent. This all mimics the net/http
package of Go that has the same functionality.
Taking an excerpt from the test:
req := new(dns.Msg)
if test.qtype == 0 {
test.qtype = dns.TypeTXT
}
req.SetQuestion(dns.Fqdn(test.qname), test.qtype)
em.Next = test.next
rec := middleware.NewResponseRecorder(&middleware.TestResponseWriter{})
code, err := em.ServeDNS(ctx, rec, req)
/* ... */
Calling em.ServeDNS
with the recorder will record the returned message that can retrieved with
rec.Msg()
.
Setup⌗
The setup function for Chaos, parses the Corefile and returns the handler, the whole function is small enough to display here:
func Chaos(c *Controller) (middleware.Middleware, error) {
version, authors, err := chaosParse(c)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return chaos.Chaos{
Next: next,
Version: version,
Authors: authors,
}
}, nil
}
This just fills in the Chaos structure and returns it as a middleware.Handler. This also needs
tests which should test some example Corefile invocations, see core/setup/chaos_test.go
for an
example.
Documentation⌗
Third, write a short document detailing the middleware, see chaos.md
as an example. It should
explain what the middleware does, how to use it and contain some examples. It will
need an Introduction, a Syntax and an Example section.
Registration⌗
The file core/directives.go
contain all the middleware for CoreDNS. This is the place where you’ll
let CoreDNS know that your new middleware exists. For chaos we just add
{"chaos", setup.Chaos},
In the list. The index in the list is important, because this will be the order in which the middleware will be called.
Helper functions⌗
As said the state
structure contains a lot of helper function, for instance Family
:
func (s State) Family() int
Family returns the family of the transport. 1 for IPv4 and 2 for IPv6.
There are other like Name
and Zones
which allow for easier name matching in your middleware.
These are likely to improved and expanded in the future, think of item like getting the UDP buffer
size, DO bit, etc. etc..