aboutsummaryrefslogtreecommitdiff
path: root/plugin/chaos
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/chaos')
-rw-r--r--plugin/chaos/README.md46
-rw-r--r--plugin/chaos/chaos.go62
-rw-r--r--plugin/chaos/chaos_test.go80
-rw-r--r--plugin/chaos/setup.go55
-rw-r--r--plugin/chaos/setup_test.go54
5 files changed, 297 insertions, 0 deletions
diff --git a/plugin/chaos/README.md b/plugin/chaos/README.md
new file mode 100644
index 000000000..4c43590e5
--- /dev/null
+++ b/plugin/chaos/README.md
@@ -0,0 +1,46 @@
+# chaos
+
+The *chaos* plugin allows CoreDNS to respond to TXT queries in the CH class.
+
+This is useful for retrieving version or author information from the server.
+
+## Syntax
+
+~~~
+chaos [VERSION] [AUTHORS...]
+~~~
+
+* **VERSION** is the version to return. Defaults to `CoreDNS-<version>`, if not set.
+* **AUTHORS** is what authors to return. No default.
+
+Note that you have to make sure that this plugin will get actual queries for the
+following zones: `version.bind`, `version.server`, `authors.bind`, `hostname.bind` and
+`id.server`.
+
+## Examples
+
+Specify all the zones in full.
+
+~~~ corefile
+version.bind version.server authors.bind hostname.bind id.server {
+ chaos CoreDNS-001 info@coredns.io
+}
+~~~
+
+Or just default to `.`:
+
+~~~ corefile
+. {
+ chaos CoreDNS-001 info@coredns.io
+}
+~~~
+
+And test with `dig`:
+
+~~~ txt
+% dig @localhost CH TXT version.bind
+...
+;; ANSWER SECTION:
+version.bind. 0 CH TXT "CoreDNS-001"
+...
+~~~
diff --git a/plugin/chaos/chaos.go b/plugin/chaos/chaos.go
new file mode 100644
index 000000000..c9811fbd0
--- /dev/null
+++ b/plugin/chaos/chaos.go
@@ -0,0 +1,62 @@
+// Package chaos implements a plugin that answer to 'CH version.bind TXT' type queries.
+package chaos
+
+import (
+ "os"
+
+ "github.com/coredns/coredns/plugin"
+ "github.com/coredns/coredns/request"
+
+ "github.com/miekg/dns"
+ "golang.org/x/net/context"
+)
+
+// Chaos allows CoreDNS to reply to CH TXT queries and return author or
+// version information.
+type Chaos struct {
+ Next plugin.Handler
+ Version string
+ Authors map[string]bool
+}
+
+// ServeDNS implements the plugin.Handler interface.
+func (c Chaos) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
+ state := request.Request{W: w, Req: r}
+ if state.QClass() != dns.ClassCHAOS || state.QType() != dns.TypeTXT {
+ return plugin.NextOrFailure(c.Name(), c.Next, ctx, w, r)
+ }
+
+ m := new(dns.Msg)
+ m.SetReply(r)
+
+ hdr := dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0}
+ switch state.Name() {
+ default:
+ return c.Next.ServeDNS(ctx, w, r)
+ case "authors.bind.":
+ for a := range c.Authors {
+ m.Answer = append(m.Answer, &dns.TXT{Hdr: hdr, Txt: []string{trim(a)}})
+ }
+ case "version.bind.", "version.server.":
+ m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{trim(c.Version)}}}
+ case "hostname.bind.", "id.server.":
+ hostname, err := os.Hostname()
+ if err != nil {
+ hostname = "localhost"
+ }
+ m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{trim(hostname)}}}
+ }
+ state.SizeAndDo(m)
+ w.WriteMsg(m)
+ return 0, nil
+}
+
+// Name implements the Handler interface.
+func (c Chaos) Name() string { return "chaos" }
+
+func trim(s string) string {
+ if len(s) < 256 {
+ return s
+ }
+ return s[:255]
+}
diff --git a/plugin/chaos/chaos_test.go b/plugin/chaos/chaos_test.go
new file mode 100644
index 000000000..332d90381
--- /dev/null
+++ b/plugin/chaos/chaos_test.go
@@ -0,0 +1,80 @@
+package chaos
+
+import (
+ "testing"
+
+ "github.com/coredns/coredns/plugin"
+ "github.com/coredns/coredns/plugin/pkg/dnsrecorder"
+ "github.com/coredns/coredns/plugin/test"
+
+ "github.com/miekg/dns"
+ "golang.org/x/net/context"
+)
+
+func TestChaos(t *testing.T) {
+ em := Chaos{
+ Version: version,
+ Authors: map[string]bool{"Miek Gieben": true},
+ }
+
+ tests := []struct {
+ next plugin.Handler
+ qname string
+ qtype uint16
+ expectedCode int
+ expectedReply string
+ expectedErr error
+ }{
+ {
+ next: test.NextHandler(dns.RcodeSuccess, nil),
+ qname: "version.bind",
+ expectedCode: dns.RcodeSuccess,
+ expectedReply: version,
+ expectedErr: nil,
+ },
+ {
+ next: test.NextHandler(dns.RcodeSuccess, nil),
+ qname: "authors.bind",
+ expectedCode: dns.RcodeSuccess,
+ expectedReply: "Miek Gieben",
+ expectedErr: nil,
+ },
+ {
+ next: test.NextHandler(dns.RcodeSuccess, nil),
+ qname: "authors.bind",
+ qtype: dns.TypeSRV,
+ expectedCode: dns.RcodeSuccess,
+ expectedErr: nil,
+ },
+ }
+
+ ctx := context.TODO()
+
+ for i, tc := range tests {
+ req := new(dns.Msg)
+ if tc.qtype == 0 {
+ tc.qtype = dns.TypeTXT
+ }
+ req.SetQuestion(dns.Fqdn(tc.qname), tc.qtype)
+ req.Question[0].Qclass = dns.ClassCHAOS
+ em.Next = tc.next
+
+ rec := dnsrecorder.New(&test.ResponseWriter{})
+ code, err := em.ServeDNS(ctx, rec, req)
+
+ if err != tc.expectedErr {
+ t.Errorf("Test %d: Expected error %v, but got %v", i, tc.expectedErr, err)
+ }
+ if code != int(tc.expectedCode) {
+ t.Errorf("Test %d: Expected status code %d, but got %d", i, tc.expectedCode, code)
+ }
+ if tc.expectedReply != "" {
+ answer := rec.Msg.Answer[0].(*dns.TXT).Txt[0]
+ if answer != tc.expectedReply {
+ t.Errorf("Test %d: Expected answer %s, but got %s", i, tc.expectedReply, answer)
+ }
+ }
+ }
+}
+
+const version = "CoreDNS-001"
diff --git a/plugin/chaos/setup.go b/plugin/chaos/setup.go
new file mode 100644
index 000000000..2064f4eae
--- /dev/null
+++ b/plugin/chaos/setup.go
@@ -0,0 +1,55 @@
+package chaos
+
+import (
+ "github.com/coredns/coredns/core/dnsserver"
+ "github.com/coredns/coredns/plugin"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("chaos", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+
+}
+
+func setup(c *caddy.Controller) error {
+ version, authors, err := chaosParse(c)
+ if err != nil {
+ return plugin.Error("chaos", err)
+ }
+
+ dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
+ return Chaos{Next: next, Version: version, Authors: authors}
+ })
+
+ return nil
+}
+
+func chaosParse(c *caddy.Controller) (string, map[string]bool, error) {
+ // Set here so we pick up AppName and AppVersion that get set in coremain's init().
+ chaosVersion = caddy.AppName + "-" + caddy.AppVersion
+
+ version := ""
+ authors := make(map[string]bool)
+
+ for c.Next() {
+ args := c.RemainingArgs()
+ if len(args) == 0 {
+ return chaosVersion, nil, nil
+ }
+ if len(args) == 1 {
+ return args[0], nil, nil
+ }
+ version = args[0]
+ for _, a := range args[1:] {
+ authors[a] = true
+ }
+ return version, authors, nil
+ }
+ return version, authors, nil
+}
+
+var chaosVersion string
diff --git a/plugin/chaos/setup_test.go b/plugin/chaos/setup_test.go
new file mode 100644
index 000000000..6f3c13fb3
--- /dev/null
+++ b/plugin/chaos/setup_test.go
@@ -0,0 +1,54 @@
+package chaos
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/mholt/caddy"
+)
+
+func TestSetupChaos(t *testing.T) {
+ tests := []struct {
+ input string
+ shouldErr bool
+ expectedVersion string // expected version.
+ expectedAuthor string // expected author (string, although we get a map).
+ expectedErrContent string // substring from the expected error. Empty for positive cases.
+ }{
+ // positive
+ {
+ `chaos v2`, false, "v2", "", "",
+ },
+ {
+ `chaos v3 "Miek Gieben"`, false, "v3", "Miek Gieben", "",
+ },
+ }
+
+ for i, test := range tests {
+ c := caddy.NewTestController("dns", test.input)
+ version, authors, err := chaosParse(c)
+
+ if test.shouldErr && err == nil {
+ t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
+ }
+
+ if err != nil {
+ if !test.shouldErr {
+ t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
+ }
+
+ if !strings.Contains(err.Error(), test.expectedErrContent) {
+ t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
+ }
+ }
+
+ if !test.shouldErr && version != test.expectedVersion {
+ t.Errorf("Chaos not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedVersion, version)
+ }
+ if !test.shouldErr && authors != nil {
+ if _, ok := authors[test.expectedAuthor]; !ok {
+ t.Errorf("Chaos not correctly set for input %s. Expected: '%s', actual: '%s'", test.input, test.expectedAuthor, "Miek Gieben")
+ }
+ }
+ }
+}