diff options
Diffstat (limited to 'plugin/chaos')
-rw-r--r-- | plugin/chaos/README.md | 46 | ||||
-rw-r--r-- | plugin/chaos/chaos.go | 62 | ||||
-rw-r--r-- | plugin/chaos/chaos_test.go | 80 | ||||
-rw-r--r-- | plugin/chaos/setup.go | 55 | ||||
-rw-r--r-- | plugin/chaos/setup_test.go | 54 |
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") + } + } + } +} |