Writing CoreDNS Middleware

March 25, 2016

dns

If it is not for me, give it to the next one.

Writing CoreDNS middleware consists out of four parts:

  1. The actual middleware; the ServeDNS method that gets the request.
  2. The setup part, the gets the Corefile configuration and creates the middleware.
  3. Documentation.
  4. 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..

CoreDNS  DNS  Tutorial