mirror of
https://github.com/coredns/coredns.io.git
synced 2026-10-19 16:15:37 +00:00
docs: cleanup (#167)
This cleans up and pulls some things in the modern age. Remove text, tweak other bits. Make it easier to consume. Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
@@ -6,48 +6,37 @@ title = "Writing Plugins for CoreDNS"
|
||||
author = "miek"
|
||||
+++
|
||||
|
||||
As CoreDNS uses Caddy for setting up and using plugin, the process of writing plugin is
|
||||
remarkably [similar](https://github.com/mholt/caddy/wiki/Writing-a-Plugin:-Directives). This post
|
||||
slightly reworks (and simplifies in some cases) those pages.
|
||||
|
||||
A new plugin adds new functionality to CoreDNS, i.e. *caching*, *metrics* and basic *zone* file
|
||||
serving are all plugins.
|
||||
A plugin adds functionality to CoreDNS, i.e. *caching*, *metrics* and basic *zone* file serving are
|
||||
all plugins.
|
||||
|
||||
If you want to write a new plugin and want it to be included by default, i.e. merged in the code
|
||||
base please open an [issue](https://github.com/coredns/coredns/issues) first to discuss initial design
|
||||
and other things that may come up.
|
||||
base please open an [issue](https://github.com/coredns/coredns/issues) first to discuss initial
|
||||
design and other things that may come up. Starting with a README file to explain how things work
|
||||
from a user perspective is usually a good idea.
|
||||
|
||||
## How to Register a CoreDNS Plugin
|
||||
See the [example plugin](https://github.com/coredns/example) for, uh, an example for how to
|
||||
structure, write and test a plugin. There are plenty of comments in the code to help you along.
|
||||
|
||||
[Caddy](https://caddyserver.com) supports different type of plugins, CoreDNS, for instance,
|
||||
registers itself as a Caddy server type plugin. CoreDNS currently supports one type of plugins;
|
||||
a *plugin*.
|
||||
## How to Register a CoreDNS Plugin?
|
||||
|
||||
Start a new Go package with an init function. Then register your plugin:
|
||||
When writing your plugin code you will need to register it with CoreDNS. This can be done by calling
|
||||
the following function:
|
||||
|
||||
```go
|
||||
import "github.com/mholt/caddy"
|
||||
|
||||
func init() {
|
||||
caddy.RegisterPlugin("foo", caddy.Plugin{
|
||||
ServerType: "dns",
|
||||
Action: setup,
|
||||
})
|
||||
}
|
||||
func init() { plugin.Register("foo", setup) }
|
||||
```
|
||||
|
||||
Every plugin must have a name, `foo`, in this case. The *ServerType* must be `dns`. The *Action*
|
||||
speficied here is to say it will call a function called `setup` whenever the *directive* `foo` is
|
||||
encountered in the Corefile.
|
||||
Every plugin must have a name, `foo`, in this case. When `foo` is encountered in the configuration
|
||||
the `setup` function will be called in this package.
|
||||
|
||||
### The Setup Function
|
||||
|
||||
The *Action* field of the `caddy.Plugin` struct is what makes a directive plugin unique. This is the
|
||||
function to run when CoreDNS is parsing and executing the Corefile.
|
||||
The `setup` function (it may be called different, but pretty much every plugin just calls it
|
||||
`setup`) parses the configuration and populates internal data structures.
|
||||
|
||||
The action is simply a function that takes a caddy.Controller and returns an error:
|
||||
(We use [plugin.Error](https://godoc.org/github.com/coredns/coredns/plugin#Error) to prefix
|
||||
returned error with `plugin/foo: ` to improve error reporting).
|
||||
The setup function a `caddy.Controller` and returns an error: (We use
|
||||
[plugin.Error](https://godoc.org/github.com/coredns/coredns/plugin#Error) to prefix returned error
|
||||
with `plugin/foo:` to improve error reporting).
|
||||
|
||||
``` go
|
||||
func setup(c *caddy.Controller) error {
|
||||
@@ -55,50 +44,38 @@ func setup(c *caddy.Controller) error {
|
||||
return plugin.Error("foo", err)
|
||||
}
|
||||
|
||||
// various other code
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
It is the responsibility of the setup function to parse the directive's tokens and configure itself.
|
||||
The [Controller struct](https://godoc.org/github.com/mholt/caddy#Controller)
|
||||
makes this quite easy. If we expect a line in the Corefile such as:
|
||||
If we see a line in the Corefile such as:
|
||||
|
||||
```
|
||||
foo gizmo
|
||||
```
|
||||
|
||||
We can get the value of the first argument ("foobar") like so:
|
||||
We can get the value of the first argument ("gizmo") like so:
|
||||
|
||||
```go
|
||||
for c.Next() { // Skip the directive name.
|
||||
for c.Next() { // Skip the plugin name, "foo" in this case.
|
||||
if !c.NextArg() { // Expect at least one value.
|
||||
return c.ArgErr() // Otherwise it's an error.
|
||||
}
|
||||
value := c.Val() // Use the value.
|
||||
}
|
||||
```
|
||||
You parse the tokens present for your directive by iterating over `c.Next()` which is true as long
|
||||
as there are more tokens to parse. Since a directive may appear multiple times, you must iterate
|
||||
over `c.Next()` to get all the appearances of your directive and consume the first token (which is the
|
||||
directive name).
|
||||
You parse the tokens present for your plugin by iterating over `c.Next()` which is true as long
|
||||
as there are more tokens to parse. Since a plugin may appear multiple times, you must iterate over
|
||||
`c.Next()` to get all the appearances of your plugin and consume the tokens.
|
||||
|
||||
### Adding to CoreDNS
|
||||
|
||||
To plug your plugin into CoreDNS, import it. This is done in
|
||||
[core/coredns.go](https://github.com/coredns/coredns/blob/master/core/coredns.go):
|
||||
To plug your plugin into CoreDNS, put in
|
||||
[`plugin.cfg`](https://github.com/coredns/coredns/blob/master/plugin.cfg) and run `go generate`.
|
||||
|
||||
|
||||
```go
|
||||
import _ "your/plugin/package/path/here"
|
||||
```
|
||||
|
||||
This makes CoreDNS compile your plugin, but it is still not available, so the second step is
|
||||
to add it to [directives.go](https://github.com/coredns/coredns/blob/master/core/dnsserver/directives.go):
|
||||
|
||||
Add the name (`foo`) of your plugin at the end of the file in the `directives` string slice.
|
||||
Note the ordering is important, because this is determines how the plugins are chained together.
|
||||
|
||||
## How DNS Plugin Works in CoreDNS
|
||||
## How A Plugin Works in CoreDNS
|
||||
|
||||
Check out the [godoc for the plugin
|
||||
package](http://godoc.org/github.com/coredns/coredns/plugin). The most important type is
|
||||
@@ -110,9 +87,8 @@ up an DNS server for you, but you need to implement these two types.
|
||||
### Writing a Handler
|
||||
|
||||
`plugin.Handler` is an interface similar to `http.Handler` except that it deals with DNS and the
|
||||
`ServeDNS` method returns `(int, error)`. The `int` is the DNS rcode, and the `error` is one that
|
||||
should be handled and/or logged. Read the
|
||||
[plugin.md](https://github.com/coredns/coredns/blob/master/plugin.md) doc for more details
|
||||
`ServeDNS` method returns `(int, error)`. The `int` is status code, and the `error` is logged (if
|
||||
not nil) See [plugin.md](https://github.com/coredns/coredns/blob/master/plugin.md) for more details
|
||||
about these return values.
|
||||
|
||||
Handlers are usually a struct with at least one field, the next Handler in the chain:
|
||||
@@ -123,9 +99,9 @@ type MyHandler struct {
|
||||
}
|
||||
```
|
||||
|
||||
To implement the `plugin.Handler` interface, we write a method called `ServeDNS`.
|
||||
This method is the actual handler function, and, unless it fully handles the request by itself, it
|
||||
should call the next handler in the chain:
|
||||
To implement the `plugin.Handler` interface, we write a method called `ServeDNS`. This method is the
|
||||
actual handler function, and, unless it fully handles the request by itself, it should call the next
|
||||
handler in the chain:
|
||||
|
||||
```go
|
||||
func (h MyHandler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
@@ -133,46 +109,16 @@ func (h MyHandler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
|
||||
}
|
||||
```
|
||||
|
||||
The interface also needs a method `func Name() string`, this is mainly used to see if an plugin
|
||||
is active, i.e. the *auto* plugin used this to detect if *metrics* are active and if so, adds
|
||||
updates the zone info.
|
||||
The interface also needs a method `func Name() string`.,
|
||||
|
||||
```go
|
||||
func (h MyHandler) Name() string { return "foo" }
|
||||
```
|
||||
|
||||
That's all there is to it.
|
||||
That's all there is to it (apart from writing all code that actually does something with the DNS
|
||||
request of course).
|
||||
|
||||
## How to Add the Handler to CoreDNS
|
||||
|
||||
So, back in your setup function. You've just parsed the tokens and set up your plugin handler
|
||||
with all the proper configuration:
|
||||
|
||||
```go
|
||||
func setup(c *caddy.Controller) error {
|
||||
for c.Next() {
|
||||
// Get configuration.
|
||||
}
|
||||
|
||||
// Now what?
|
||||
}
|
||||
```
|
||||
|
||||
To chain in your new handler, get the config for the current site from the dnsserver package.
|
||||
Then wrap your handler in a plugin function:
|
||||
|
||||
```go
|
||||
cfg := dnsserver.GetConfig(c)
|
||||
mid := func(next plugin.Handler) plugin.Handler {
|
||||
return MyHandler{Next: next}
|
||||
}
|
||||
cfg.AddPlugin(mid)
|
||||
```
|
||||
|
||||
And you're done! Of course, in this example, we simply allocated a `MyHandler` with no special
|
||||
configuration. It doesn't really matter as long as you chain in the `next` handler properly!
|
||||
|
||||
## Examples
|
||||
## Further Reading
|
||||
|
||||
Simple examples of plugin that can be found in CoreDNS are:
|
||||
|
||||
@@ -180,6 +126,8 @@ Simple examples of plugin that can be found in CoreDNS are:
|
||||
a plugin. It simply performs some setup.
|
||||
* [chaos](https://godoc.org/github.com/coredns/coredns/plugin/chaos); a DNS plugin that
|
||||
responds to `CH txt version.bind` requests.
|
||||
* [example](https://github.com/coredns/example); an example plugin that prints "example" when
|
||||
responding to a query.
|
||||
|
||||
**Don't forget: the best documentation is the [godoc](https://godoc.org/github.com/coredns/coredns)
|
||||
and the [code](https://github.com/coredns/coredns) itself!**
|
||||
|
||||
@@ -10,8 +10,7 @@ author = "miek"
|
||||
CoreDNS' [plugins](/plugins) (or [external plugins](/explugins)) can be enabled or
|
||||
disabled on the fly by specifying (or not specifying) it in the
|
||||
[Corefile](/2017/07/23/corefile-explained/). But you can also compile CoreDNS with only the
|
||||
plugin you *need* and leave the rest completely out.
|
||||
|
||||
plugins you *need* and leave the rest completely out.
|
||||
|
||||
There are two ways to achieve that. It could be done via compile-time configuration file
|
||||
with CoreDNS code base update. It also could be achieved without modifying CoreDNS code.
|
||||
@@ -26,7 +25,7 @@ to update. It looks like this:
|
||||
...
|
||||
whoami:whoami
|
||||
erratic:erratic
|
||||
startup:github.com/mholt/caddy/startupshutdown
|
||||
federation:github.com/coredns/federation
|
||||
...
|
||||
~~~
|
||||
|
||||
@@ -82,5 +81,5 @@ The directives should also be updated through:
|
||||
|
||||
The ordering of the plugins is specified by how the arey ordered in the slice `directives`.
|
||||
|
||||
Then you can just compile CoreDNS with `go build` to have the binary generated with the
|
||||
plugins you selected.
|
||||
Then you can just compile CoreDNS with `go build` to have the binary generated with the␍ plugins you
|
||||
selected.
|
||||
|
||||
@@ -42,22 +42,13 @@ Typically a plugin has a file called `setup.go` that handles the registration.
|
||||
In there, the `init` function should look like:
|
||||
|
||||
~~~ go
|
||||
func init() {
|
||||
caddy.RegisterPlugin("whoami", caddy.Plugin{
|
||||
ServerType: "dns",
|
||||
Action: setupWhoami,
|
||||
})
|
||||
}
|
||||
func init() { plugin.Register("whoami", setup) }
|
||||
~~~
|
||||
First you see that we're using `caddy.RegisterPlugin` which makes perfectly sense, because
|
||||
CoreDNS uses the [Caddy](https://caddyserver.com) server framework. The first argument is the name
|
||||
of the *plugin* as used in the Corefile.
|
||||
|
||||
The `ServerType` is "dns" because CoreDNS is a DNS server; Caddy supports HTTP servers as well. The
|
||||
`Action` is the name of the setup function that takes care of the parsing of the Corefile. Its job
|
||||
`setup` is the name of the setup function that takes care of the parsing of the Corefile. Its job
|
||||
is to return a type that implements the `plugin.Handler` interface.
|
||||
|
||||
So whenever the Corefile parser sees "whoami", `setupWhoami` is called.
|
||||
So whenever the Corefile parser sees "whoami", `whoami.setup` is called.
|
||||
|
||||
# 2. Setup function
|
||||
|
||||
@@ -79,7 +70,7 @@ func setupWhoami(c *caddy.Controller) error {
|
||||
~~~
|
||||
We use the `*caddy.Controller` to receive tokens from the Corefile and act upon them. Here we only
|
||||
check if there is nothing specified after the token `whoami`. If you need to do more there are
|
||||
`c.Val()`, `c.Args()` and [friends](https://godoc.org/github.com/mholt/caddy/caddyfile#Dispenser).
|
||||
`c.Val()`, `c.Args()` and [friends](https://godoc.org/github.com/caddyserver/caddy/caddyfile#Dispenser).
|
||||
|
||||
The full `setup.go` for *whoami* is [here](https://github.com/miekg/coredns/blob/master/plugin/whoami/setup.go).
|
||||
|
||||
@@ -118,12 +109,11 @@ Next we setup the reply message:
|
||||
~~~ go
|
||||
a := &dns.Msg{}
|
||||
a.SetReply(r)
|
||||
a.Compress = true
|
||||
a.Authoritative = true
|
||||
~~~
|
||||
|
||||
We create a new message and copy the relevant bits from the incoming reply to the one we're planning
|
||||
to return. We fiddle with some message bits and turn on message compression.
|
||||
to return. We fiddle with some message bits and set it to authoritative.
|
||||
|
||||
Then we are going to inspect the incoming message via the `state` helper struct to see what we
|
||||
should return.
|
||||
@@ -167,18 +157,14 @@ In the last bit of this method we create the full message and send it:
|
||||
|
||||
~~~go
|
||||
a.Extra = []dns.RR{rr, srv}
|
||||
|
||||
state.SizeAndDo(a)
|
||||
w.WriteMsg(a)
|
||||
|
||||
return 0, nil
|
||||
~~~
|
||||
|
||||
First we add the two created resource records (`rr` and `srv`) to the additional section of the
|
||||
answer message: `a.Extra`.
|
||||
Then we call `state.SizeOnDo(a)` which will set the correct EDNS0 record on the message and handle
|
||||
setting the truncation bit). Then,
|
||||
finally, we call the `WriteMsg` method on `w` which writes the message back to the client.
|
||||
|
||||
Then, finally, we call the `WriteMsg` method on `w` which writes the message back to the client.
|
||||
We indicate a successful (even though it might have failed - we didn't check the return value of
|
||||
`WriteMsg`) write by returning 0 and `nil`.
|
||||
|
||||
@@ -194,13 +180,13 @@ whoami:whoami
|
||||
The initial number is used for sorting the plugin (more on that below), then the name of the
|
||||
plugin as used in the registration and then the package inside the CoreDNS plugin directory.
|
||||
|
||||
Each plugin has a place in the list of all other ones. For instance the caching
|
||||
or the metrics plugin needs to come early, so that it can "see" the query and
|
||||
response and do cachy or metricsy things with it. A *whoami* plugin is not that special and can
|
||||
be placed relatively late in the list (hence the high number).
|
||||
Each plugin has a place in the list of all other ones. For instance the caching or the metrics
|
||||
plugin needs to come early, so that it can "see" the query and response and do cachy or metricsy
|
||||
things with it. A *whoami* plugin is not that special and can be placed relatively late in the list
|
||||
(hence the high number).
|
||||
|
||||
Now do a `make` (or `go generate && go build`) to get a `coredns` binary that can be used with our shiny new plugin.
|
||||
This binary should include `dns.whoami` when called with `-plugins`.
|
||||
Now do a `make` (or `go generate && go build`) to get a `coredns` binary that can be used with our
|
||||
shiny new plugin. This binary should include `dns.whoami` when called with `-plugins`.
|
||||
|
||||
# 5. Using it
|
||||
|
||||
@@ -214,15 +200,18 @@ Write a Corefile:
|
||||
In words this says: be authoritative for the root `.` and below, meaning all possible queries will
|
||||
hit this stanza. And for each request call *whoami*.
|
||||
|
||||
Start CoreDNS with: `coredns -conf Corefile -dns.port 1053`. Couple of notes here. CoreDNS
|
||||
will look for a Corefile in the current directory so the `-conf Corefile` is only given here for
|
||||
completeness. The `-dns.port` will start CoreDNS on port 1053, so we don't need to run as root.
|
||||
Start CoreDNS with: `coredns -conf Corefile -dns.port 1053`. Couple of notes here. CoreDNS will look
|
||||
for a Corefile in the current directory so the `-conf Corefile` is only given here for completeness.
|
||||
The `-dns.port` will start CoreDNS on port 1053, so we don't need to run as root.
|
||||
|
||||
CoreDNS will output the following:
|
||||
~~~ txt
|
||||
.:1053
|
||||
2017/02/19 17:44:31 [INFO] CoreDNS-005
|
||||
CoreDNS-005
|
||||
______ ____ _ _______
|
||||
/ ____/___ ________ / __ \/ | / / ___/ ~ CoreDNS-1.6.4
|
||||
/ / / __ \/ ___/ _ \/ / / / |/ /\__ \ ~ linux/amd64, go1.13.1, b139ba3
|
||||
/ /___/ /_/ / / / __/ /_/ / /| /___/ /
|
||||
\____/\____/_/ \___/_____/_/ |_//____/
|
||||
~~~
|
||||
The `.:1053` indicates it has parsed our Corefile and is listening on port 1053 for queries for
|
||||
the root `.` zone and below.
|
||||
@@ -245,8 +234,11 @@ _tcp.example.org. 0 IN SRV 0 0 33435 .
|
||||
~~~
|
||||
Yes, it correctly sees we've used TCP this time (and of course a different port).
|
||||
|
||||
There are already a lot of different plugin in CoreDNS. New, exciting ones, are always welcome. So if you
|
||||
have an idea open an issue on the tracker.
|
||||
There are already a lot of different plugin in CoreDNS. New, exciting ones, are always welcome. So
|
||||
if you have an idea open an issue on the tracker.
|
||||
|
||||
This is an updated version of the [previous
|
||||
blog](/2016/12/19/writing-plugins-for-coredns/) about writing CoreDNS plugin.
|
||||
This is an updated version of the [previous blog](/2016/12/19/writing-plugins-for-coredns/) about
|
||||
writing CoreDNS plugin.
|
||||
|
||||
Also see the [example plugin](https://github.com/coredns/example) for a simpler, plugin that just
|
||||
serves as an example.
|
||||
|
||||
@@ -7,18 +7,20 @@ author = "miek"
|
||||
+++
|
||||
|
||||
The `plugin.md` in the CoreDNS source tree has some pointers on what a plugin for CoreDNS
|
||||
should have as minimum requirements. It basically boils down to: documentation, tests and working.
|
||||
should have as minimum requirements. It basically boils down to: "it should add something unique and
|
||||
useful to CoreDNS". Further more documentation, tests and functionality should all be excellent.
|
||||
|
||||
It is easier to list when a plugin can be *included* in CoreDNS than to say it should stay
|
||||
external, so we will do the first:
|
||||
external, so we will do that:
|
||||
|
||||
* First, the plugin should be useful for *other* people. "Useful" is a subjective term, but the
|
||||
plugin needs to fill a niche that appeals to more than one person.
|
||||
* It should be sufficiently different from other plugin to warrant inclusion.
|
||||
* It should be sufficiently different from other plugins to warrant inclusion.
|
||||
* Current internet standards need be supported: IPv4 and IPv6, so A and AAAA records should be
|
||||
handled (if your plugin is in the business of dealing with address records that is).
|
||||
* It must have tests.
|
||||
* It must have a README.md for documentation.
|
||||
* Care must be taken to make it efficient in both memory and CPU.
|
||||
|
||||
Plugin for CoreDNS can live out-of-tree, `plugin.cfg` defaults to CoreDNS' repo but other
|
||||
repos work just as well, so it is pretty easy to use your plugin even though it is out-of-tree.
|
||||
Plugins for CoreDNS can easily live out-of-tree, `plugin.cfg` defaults to CoreDNS' repo but other
|
||||
repos work just as well.
|
||||
|
||||
Reference in New Issue
Block a user