Developing Custom Plugins for CoreDNS
Tags: tutorial, go, linux, programming
Published: 2024-06-30 14:05:27

---
title: Developing Custom Plugins for CoreDNS
published: true
description: Develop and Run Custom Plugins for CoreDNS
tags: tutorial,go,linux,programming
---

## Introduction To CoreDNS

CoreDNS is a powerful, flexible DNS server written in Go. One of its key features is its plugin-based architecture, which allows users to extend its functionality easily. In this blog post, we'll explore how to write custom plugins for CoreDNS.


![CoreDNS Flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f8nkdkngrvgllfh9ytya.png)

As previously mentioned, CoreDNS utilizes a plugin chain architecture, enabling you to stack multiple plugins that execute sequentially. Most of CoreDNS's functionality is provided by its built-in plugins. You can explore these bundled plugins by [Clicking here](https://github.com/coredns/coredns/tree/master/plugin).

## Architecture Overview

CoreDNS follows a similar approach to Caddy, as it is based on Caddy v1:

- **Load Configuration**: Configuration is loaded through the `Corefile` file.
- **Plugin Setup**: Plugins must implement a `setup` function to load, validate the configuration, and initialize the plugin.
- **Handler Implementation**: You need to implement the required functions from the `plugin.Handler` interface.
- **Integrate Your Plugin**: Add your plugin to CoreDNS by either including it in the `plugin.cfg` file or by wrapping everything in an external source code. Further details can be found below.

## Develop

### Configuration

As mentioned above, everything is done through the `Corefile`. If you're not familiar with the syntax, check this short explanation: https://coredns.io/2017/07/23/corefile-explained/

```
. {
    foo
}
```

In the example above, `.` defines a server block, and `foo` is the name of your plugin. You can specify a port or add arguments to your plugin.

```
.:5353 {
    foo bar
}
```
Now CoreDNS is running on port `5353` and my plugin named `foo` is given the argument `bar`.

> It's useful to enable the plugins `log` and `debug` during the development.

Checkout the list of bundled plugins to figure out which ones you need in your setup: https://coredns.io/plugins/

### Setup

The first thing you need to do is to register and set up your plugin. Registration is done through a function called `init` which you need to include in your go module.

```go
package foo

import (
	"github.com/coredns/caddy"
	"github.com/coredns/coredns/core/dnsserver"
	"github.com/coredns/coredns/plugin"
)

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

Now we need to implement `setup()` which parses the configuration and returns our initialized plugin.

```go
package foo

import (
	"github.com/coredns/caddy"
	"github.com/coredns/coredns/core/dnsserver"
	"github.com/coredns/coredns/plugin"
)

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

func setup(c *caddy.Controller) error {
	c.Next() // #1
	if !c.NextArg() { // #2
        return c.ArgErr()
	}

	dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
		return Foo{Next: next, Bar: c.val()} // #3
	})

	return nil // #4
}
```

1. Skip the first token which is `foo`, the name of our argument.
2. Return an error if our argument didn't have any value.
3. Put the value of our argument `bar` in the plugin struct and return it to be put in the plugin chain. Read more details on the plugin struct further down.
4. return `nil` as an error if everything is good to go.

### Handler
All plugins need to implement `plugin.Handler` which is the entry point to your plugin.

First, we need to write a struct containing the necessary arguments, runtime objects, and also the next plugin in the chain.

```go
type Foo struct {
    Bar string
    Next plugin.Handler
}
```
> This is the actual struct that we created in the previous step.


We also need a method to return the name of the plugin.

```go
func (h Foo) Name() string { return "foo" }
```

Now it's time for the most important method which is `ServeDNS()`. This is the method that is called for every DNS query routed to your plugin. You can also generate a response here making your plugin work as a data backend.

```go
func (h Foo) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {

  return h.Next.ServeDNS(ctx, w, r)

}
```
What you see here does nothing but call the next plugin in the chain. But we don't have to do that :)

Use `r *dns.Msg` to get some info on the DNS query.
```go
state := request.Request{W: w, Req: r}
qname := state.Name()
```

List of variables you can get from `state`:
- `state.Name()` name of the query - includes the zone as well
- `state.Type()` type of the query - e.g. `A`, `AAAA`, etc
- `state.Ip()` IP address of the client making the request
- `state.Proto()` transport protocol - `tcp` or `udp`
- `state.Family()` IP version - `1` for IPv4 and `2` for IPv6
> Read the following file for the complete list: https://github.com/coredns/coredns/blob/master/request/request.go


You can also generate a response and return from the chain. For that, you need to use the amazing `github.com/miekg/dns` package and build a `dns.Msg` to return.

```go
func (h Foo) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {

    dummy_ip := "1.1.1.1"

    state := request.Request{W: w, Req: r}
	qname := state.Name()
    
    answers := make([]dns.RR, 0, 10)

    resp := new(dns.A)
    resp.Hdr = dns.RR_Header{Name: dns.Fqdn(qname), Rrtype: dns.TypeA,
        Class: dns.ClassINET, Ttl: a.Ttl}
    resp.A = net.ParseIP(dummy_ip)
    answers = append(answers, resp)


    m := new(dns.Msg)
	m.SetReply(r)
	m.Authoritative, m.RecursionAvailable, m.Compress = true, false, true

	m.Answer = append(m.Answer, answers...)

	state.SizeAndDo(m)
	m = state.Scrub(m)
	_ = w.WriteMsg(m)
	return dns.RcodeSuccess, nil
}
```

In the example shown above, we create an `A` record response and return it to the client.

> Check out the DNS package we used for more details on how to create DNS objects: https://pkg.go.dev/github.com/miekg/dns

If successful, we return `dns.RcodeSuccess`. To see more return codes, check out here: https://pkg.go.dev/github.com/miekg/dns#pkg-constants

A few important return codes:
- `RcodeSuccess`: No error
- `RcodeServerFailure`: Server failure
- `RcodeNameError`: Domain doesn't exist
- `RcodeNotImplemented`: Record type not implemented

### Logging

You can use the logging package provided by the CoreDNS itself, `github.com/coredns/coredns/plugin/pkg/log`.

```go
package Foo

import (
	clog "github.com/coredns/coredns/plugin/pkg/log"
)

var log = clog.NewWithPlugin("foo")
```

Now you can log anything you need with different levels:
```go
log.Info("info log")
log.Debug("debug log")
log.Warning("warning log")
log.Error("error log")
```

### Final Example

```go
package Foo

import (
	"github.com/coredns/coredns/request"
	"github.com/miekg/dns"
	"golang.org/x/net/context"
	clog "github.com/coredns/coredns/plugin/pkg/log"
)

var log = clog.NewWithPlugin("foo")

type Foo struct {
    Bar string
    Next plugin.Handler
}

func (h Foo) Name() string { return "foo" }

func (h Foo) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {

    dummy_ip := "1.1.1.1"

    state := request.Request{W: w, Req: r}
	qname := state.Name()
    
    answers := make([]dns.RR, 0, 10)

    resp := new(dns.A)
    resp.Hdr = dns.RR_Header{Name: dns.Fqdn(qname), Rrtype: dns.TypeA,
        Class: dns.ClassINET, Ttl: a.Ttl}
    resp.A = net.ParseIP(dummy_ip)
    answers = append(answers, resp)

	log.Debug("answers created")


    m := new(dns.Msg)
	m.SetReply(r)
	m.Authoritative, m.RecursionAvailable, m.Compress = true, false, true

	m.Answer = append(m.Answer, answers...)

	state.SizeAndDo(m)
	m = state.Scrub(m)
	_ = w.WriteMsg(m)
	return dns.RcodeSuccess, nil
}
```

## Compile

CoreDNS gives you two different ways to run your plugin, both are static builds.

### Compile-time Configuration
In this method, you need to clone the CoreDNS source code, add your plugin to the `plugin.cfg` file (plugins are ordered), and compile the code.

```
etcd:etcd
foo:github.com/you/foo
```

Then you need to do a `go get github.com/you/foo` and build the CoreDNS binary using `make`.

Run `./coredns -plugins` to ensure your plugin is included in the binary.

> If your plugin is on your local machine you can put something like `replace github.com/you/foo => ../foo` in your `go.mod` file.

### Wrapping in External Source Code

You also have the option to wrap the CoreDNS components and your plugin in an external source code and compile from there.
```go
package main

import (
    _ "github.com/you/foo"
    "github.com/coredns/coredns/coremain"
    "github.com/coredns/coredns/core/dnsserver"
)

var directives = []string{
    "foo",
    ...
    ...
    "whoami",
    "startup",
    "shutdown",
}

func init() {
    dnsserver.Directives = directives
}

func main() {
    coremain.Run()
}
```

As with any other go app, do a `go build` and you should have the binary.

## Links
- https://coredns.io/2017/06/08/how-queries-are-processed-in-coredns/
- https://coredns.io/2016/12/19/writing-plugins-for-coredns/
- https://coredns.io/2017/07/25/compile-time-enabling-or-disabling-plugins/
- https://mrkaran.dev/posts/coredns-nomad/