diff --git a/core/coredns.go b/core/coredns.go index c74f7b0a5..0ef1789b1 100644 --- a/core/coredns.go +++ b/core/coredns.go @@ -11,6 +11,7 @@ import ( _ "github.com/miekg/coredns/middleware/cache" _ "github.com/miekg/coredns/middleware/chaos" _ "github.com/miekg/coredns/middleware/dnssec" + _ "github.com/miekg/coredns/middleware/erratic" _ "github.com/miekg/coredns/middleware/errors" _ "github.com/miekg/coredns/middleware/etcd" _ "github.com/miekg/coredns/middleware/file" diff --git a/core/dnsserver/directives.go b/core/dnsserver/directives.go index 35e01b692..4d80331b8 100644 --- a/core/dnsserver/directives.go +++ b/core/dnsserver/directives.go @@ -96,4 +96,5 @@ var directives = []string{ "proxy", "httpproxy", "whoami", + "erratic", } diff --git a/middleware/erratic/README.md b/middleware/erratic/README.md new file mode 100644 index 000000000..c255e3150 --- /dev/null +++ b/middleware/erratic/README.md @@ -0,0 +1,45 @@ +# erratic + +*erratic* is a middleware useful for testing client behavior. It returns a static response to all +queries, but the responses can be delayed by a random amount of time or dropped all together, i.e. +no answer at all. + +~~~ txt +._.qname. 0 IN SRV 0 0 . +~~~ + +The *erratic* middleware will respond to every A or AAAA query. For any other type it will return +a SERVFAIL response. The reply for A will return 192.0.2.53 (see RFC 5737), for AAAA it returns +2001:DB8::53 (see RFC 3849). + +## Syntax + +~~~ txt +erratic { + drop AMOUNT +} +~~~ + +* **AMOUNT** drop 1 per **AMOUNT** of the queries, the default is 2. + +## Examples + +~~~ txt +.:53 { + erratic { + drop 3 + } +} +~~~ + +Or even shorter if the defaults suits you: + +~~~ txt +. { + erratic +} +~~~ + +## Bugs + +Delaying answers is not implemented. diff --git a/middleware/erratic/erratic.go b/middleware/erratic/erratic.go new file mode 100644 index 000000000..065ed361b --- /dev/null +++ b/middleware/erratic/erratic.go @@ -0,0 +1,74 @@ +// Package erratic implements a middleware that returns erratic answers (delayed, dropped). +package erratic + +import ( + "sync/atomic" + + "github.com/miekg/coredns/request" + + "github.com/miekg/dns" + "golang.org/x/net/context" +) + +// Erratic is a middleware that returns erratic repsonses to each client. +type Erratic struct { + amount uint64 + + q uint64 // counter of queries +} + +// ServeDNS implements the middleware.Handler interface. +func (e *Erratic) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + state := request.Request{W: w, Req: r} + + drop := false + if e.amount > 0 { + queryNr := atomic.LoadUint64(&e.q) + + if queryNr%e.amount == 0 { + drop = true + } + + atomic.AddUint64(&e.q, 1) + } + + m := new(dns.Msg) + m.SetReply(r) + m.Compress = true + m.Authoritative = true + + // small dance to copy rrA or rrAAAA into a non-pointer var that allows us to overwrite the ownername + // in a non-racy manor. + switch state.QType() { + case dns.TypeA: + rr := *(rrA.(*dns.A)) + rr.Header().Name = state.QName() + m.Answer = append(m.Answer, &rr) + case dns.TypeAAAA: + rr := *(rrAAAA.(*dns.AAAA)) + rr.Header().Name = state.QName() + m.Answer = append(m.Answer, &rr) + default: + if !drop { + // coredns will return error. + return dns.RcodeServerFailure, nil + } + } + + if drop { + return 0, nil + } + + state.SizeAndDo(m) + w.WriteMsg(m) + + return 0, nil +} + +// Name implements the Handler interface. +func (e *Erratic) Name() string { return "erratic" } + +var ( + rrA, _ = dns.NewRR(". IN 0 A 192.0.2.53") + rrAAAA, _ = dns.NewRR(". IN 0 AAAA 2001:DB8::53") +) diff --git a/middleware/erratic/erratic_test.go b/middleware/erratic/erratic_test.go new file mode 100644 index 000000000..eaf34c39b --- /dev/null +++ b/middleware/erratic/erratic_test.go @@ -0,0 +1,45 @@ +package erratic + +import ( + "testing" + + "github.com/miekg/coredns/middleware/pkg/dnsrecorder" + "github.com/miekg/coredns/middleware/test" + + "github.com/miekg/dns" + "golang.org/x/net/context" +) + +func TestErraticDrop(t *testing.T) { + e := &Erratic{amount: 2} // 50% drops + + tests := []struct { + expectedCode int + expectedErr error + drop bool + }{ + {expectedCode: dns.RcodeSuccess, expectedErr: nil, drop: true}, + {expectedCode: dns.RcodeSuccess, expectedErr: nil, drop: false}, + } + + ctx := context.TODO() + + for i, tc := range tests { + req := new(dns.Msg) + req.SetQuestion("example.org.", dns.TypeA) + + rec := dnsrecorder.New(&test.ResponseWriter{}) + code, err := e.ServeDNS(ctx, rec, req) + + if err != tc.expectedErr { + t.Errorf("Test %d: Expected error %q, but got %q", 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.drop && rec.Msg != nil { + t.Errorf("Test %d: Expected dropped packet, but got %q", i, rec.Msg.Question[0].Name) + } + } +} diff --git a/middleware/erratic/setup.go b/middleware/erratic/setup.go new file mode 100644 index 000000000..207db317b --- /dev/null +++ b/middleware/erratic/setup.go @@ -0,0 +1,59 @@ +package erratic + +import ( + "fmt" + "strconv" + + "github.com/miekg/coredns/core/dnsserver" + "github.com/miekg/coredns/middleware" + + "github.com/mholt/caddy" +) + +func init() { + caddy.RegisterPlugin("erratic", caddy.Plugin{ + ServerType: "dns", + Action: setupErratic, + }) +} + +func setupErratic(c *caddy.Controller) error { + e, err := parseErratic(c) + if err != nil { + return middleware.Error("erratic", err) + } + + dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler { + return e + }) + + return nil +} + +func parseErratic(c *caddy.Controller) (*Erratic, error) { + e := &Erratic{amount: 2} + for c.Next() { // 'erratic' + for c.NextBlock() { + switch c.Val() { + case "drop": + args := c.RemainingArgs() + if len(args) > 1 { + return nil, c.ArgErr() + } + + if len(args) == 0 { + return nil, nil + } + amount, err := strconv.ParseInt(args[0], 10, 32) + if err != nil { + return nil, err + } + if amount < 0 { + return nil, fmt.Errorf("illegal amount value given %q", args[0]) + } + e.amount = uint64(amount) + } + } + } + return e, nil +} diff --git a/middleware/erratic/setup_test.go b/middleware/erratic/setup_test.go new file mode 100644 index 000000000..99b545f5e --- /dev/null +++ b/middleware/erratic/setup_test.go @@ -0,0 +1,28 @@ +package erratic + +import ( + "testing" + + "github.com/mholt/caddy" +) + +func TestSetupWhoami(t *testing.T) { + c := caddy.NewTestController("dns", `erratic { + drop + }`) + if err := setupErratic(c); err != nil { + t.Fatalf("Test 1, expected no errors, but got: %q", err) + } + + c = caddy.NewTestController("dns", `erratic`) + if err := setupErratic(c); err != nil { + t.Fatalf("Test 2, expected no errors, but got: %q", err) + } + + c = caddy.NewTestController("dns", `erratic { + drop -1 + }`) + if err := setupErratic(c); err == nil { + t.Fatalf("Test 4, expected errors, but got: %q", err) + } +}