Wolfspyre Labs ๐บ๐ฅ๐ฌ/ Our Guides/ CoreDNS @ Wolfspyre Labs/ ๐ CoreDNS Manual/ Developing Plugins/ Developing Plugins External Plugins #CoreDNS comes with a large set of plugins, but not all (existing) plugins are included in the default install. You can find external plugins on our website, or develop one yourself. Enabling plugins is a compile time affair in CoreDNS, so you’ll need to have CoreDNS’ source code. Writing Plugins #As mentioned before in this manual, plugins are the thing that make CoreDNS tick. We’ve seen a bunch of configuration in the previous section, but how can you write your own plugin? See Writing Plugins for CoreDNS for an older post on this subject. The plugin.md documented in CoreDNS’ source also has some background and talks about styling the README.md. The canonical example plugin is the example plugin. Its github repository shows the most minimal code (with tests!) that is needed to create a plugin. It has: setup.go and setup_test.go, which implement the parsing of configuration from the Corefile. The (usually named) setup function is called whenever the Corefile parser see the plugin’s name; in this case, “example”. example.go (usually named <plugin_name>.go), which contains logic for handling the query, and example_test.go, which has basic units tests to check if the plugin works. The README.md that documents in a Unix manual style how this plugin can be configured. A LICENSE file. For inclusion in CoreDNS, this needs to have an APL like license. The code also has extensive comments; feel free to fork it and base your plugin off of it. How Plugins Are Called #When CoreDNS wants to use a plugin it calls the method ServeDNS. ServeDNS has three parameters: a context.Context; a dns.ResponseWriter that is basically the client’s connection; a *dns.Msg that is the request from the client. ServeDNS returns two values: a (response) code and an error. The error is logged when the errors is used in this server. The code tells CoreDNS if a reply has been written by the plugin chain or not. In the latter case, CoreDNS will take care of that. For the code’s values, we reuse the DNS return codes (rcodes) from the dns package. CoreDNS treats: SERVFAIL (dns.RcodeServerFailure) REFUSED (dns.RcodeRefused) FORMERR (dns.RcodeFormatError) NOTIMP (dns.RcodeNotImplemented) As special and will then assume nothing has been written to the client. In all other cases, it assumes something has been written to the client (by the plugin). See this post on how to compile CoreDNS with your plugin. Logging From a Plugin #Use the log package to add logging to your plugin. You initialize it with: var log = clog.NewWithPlugin("example") Now you can log.Infof("...") to print something to standard output prefixed with level [INFO] plugin/example. Level can be: INFO, WARNING or ERROR. In general, logging should be left to the higher layers when returning an error. However, if there is a reason to consume the error but still notify the user, then logging in the plugin can be acceptable. Metrics #When exporting metrics, the Namespace should be plugin.Namespace (=“coredns”), and the Subsystem should be the name of the plugin. The README.md for the plugin should then also contain a Metrics section detailing the metrics. If the plugin supports readiness reporting, it should also have a Ready section detailing it. Documentation #Each plugin should have a README.md explaining what the plugin does and how it is configured. The file should have the following layout: Title: use the plugin’s name Subsection titled: “Named” with <plugin name> - <one line description>., i.e. NAME DASH DESCRIPTION DOT. Subsection titled: “Description” with a longer description and all the options the plugin supports. Subsection titled: “Syntax” detailing syntax and supported directives. Subsection titled: “Examples”. Optional Subsection titled: “See Also”, that references external documentation, like IETF RFCs. Optional Subsection titled: “Bugs” that lists things that do not work yet. More sections are, of course, possible. Style #We use the Unix manual page style: The name of the plugin in the running text should be italic: *plugin*. All CAPITAL user supplied arguments in the running text reference use strong text: **EXAMPLE**. Optional text is in block quotes: [optional]. Use three dots to indicate multiple options are allowed: arg.... Item used literal: literal. Example Domain Names #Please be sure to use example.org or example.net in any examples and tests you provide. These are the standard domain names created for this purpose. If you don’t, there is a chance your fantasy domain name is registered by someone and will actually serve web content (which you may like or not). Fallthrough #In a perfect world, the following would be true for plugins: “Either you are responsible for a zone or not”. If the answer is “not”, the plugin should call the next plugin in the chain. If “yes” it should handle all names that fall in this zone and the names below - i.e. it should handle the entire domain and all sub domains, also see here on how a query is process with fallthrough enabled. . { file example.org db.example } In this example the file plugin is handling all names below (and including) example.org. If a query comes in that is not a subdomain (or equal to) example.org the next plugin is called. Now, the world isn’t perfect, and there are good reasons to “fallthrough” to the next middleware, meaning a plugin is only responsible for a subset of names within the zone. The first of these to appear was the reverse plugin, now replaced with the generalized template plugin that can synthesizes various responses. The nature of the template plugin might only deal with specified record TYPEs, and then only for a subset of the names. Ideally, you would want to layer template in front of another plugin such as file or auto. This means template could handle some special reverse cases and all other requests are handled by the backing plugin. This is exactly what “fallthrough” does. To keep things explicit we’ve opted that plugins implementing such behavior should implement a fallthrough keyword. The fallthrough directive should optionally accept a list of zones. Only queries for records in one of those zones should be allowed to fallthrough. Qualifying for main repo #See this document describing the requirements.