From d998aa6c25ffe83e2d2abf9ba52c5ada57da7e30 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Fri, 20 Jul 2018 10:25:54 +0100 Subject: [PATCH] plugin/erratic: add axfr support (#1977) * plugin/erratic: add axfr support Add support for axfr. This to fix and test long standing axfr issues that are hard to test if we don't support it directly in coredns. The most intriguing feature is withholding the last SOA from a response so the client needs to wait; drop (no reply) and delay is also supported. All TTLs are set to zero. Add simple tests that checks if first record is a SOA. Signed-off-by: Miek Gieben * more test coverage Signed-off-by: Miek Gieben --- plugin/erratic/README.md | 5 +++- plugin/erratic/erratic.go | 26 ++++++++++++----- plugin/erratic/erratic_test.go | 27 ++++++++++++++++-- plugin/erratic/xfr.go | 52 ++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 11 deletions(-) create mode 100644 plugin/erratic/xfr.go diff --git a/plugin/erratic/README.md b/plugin/erratic/README.md index ab58b161b..62625c1d0 100644 --- a/plugin/erratic/README.md +++ b/plugin/erratic/README.md @@ -10,7 +10,8 @@ The *erratic* plugin 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](https://tools.ietf.org/html/rfc5737), -for AAAA it returns 2001:DB8::53 (see [RFC 3849](https://tools.ietf.org/html/rfc3849)). +for AAAA it returns 2001:DB8::53 (see [RFC 3849](https://tools.ietf.org/html/rfc3849)) and for an +AXFR request it will respond with a small zone transfer. *erratic* can also be used in conjunction with the *autopath* plugin. This is mostly to aid in testing. @@ -30,6 +31,8 @@ erratic { * `delay`: delay 1 per **AMOUNT** of queries for **DURATION**, the default for **AMOUNT** is 2 and the default for **DURATION** is 100ms. +In case of a zone transfer and truncate the final SOA record *isn't* added to the response. + ## Health This plugin implements dynamic health checking. For every dropped query it turns unhealthy. diff --git a/plugin/erratic/erratic.go b/plugin/erratic/erratic.go index 290e0c2cc..3460f3bca 100644 --- a/plugin/erratic/erratic.go +++ b/plugin/erratic/erratic.go @@ -61,14 +61,26 @@ func (e *Erratic) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg rr := *(rrAAAA.(*dns.AAAA)) rr.Header().Name = state.QName() m.Answer = append(m.Answer, &rr) - default: - if !drop { - if delay { - time.Sleep(e.duration) - } - // coredns will return error. - return dns.RcodeServerFailure, nil + case dns.TypeAXFR: + if drop { + return 0, nil } + if delay { + time.Sleep(e.duration) + } + + xfr(state, trunc) + return 0, nil + + default: + if drop { + return 0, nil + } + if delay { + time.Sleep(e.duration) + } + // coredns will return error. + return dns.RcodeServerFailure, nil } if drop { diff --git a/plugin/erratic/erratic_test.go b/plugin/erratic/erratic_test.go index 8a3b4e011..406fd8774 100644 --- a/plugin/erratic/erratic_test.go +++ b/plugin/erratic/erratic_test.go @@ -14,19 +14,22 @@ func TestErraticDrop(t *testing.T) { e := &Erratic{drop: 2} // 50% drops tests := []struct { + rrtype uint16 expectedCode int expectedErr error drop bool }{ - {expectedCode: dns.RcodeSuccess, expectedErr: nil, drop: true}, - {expectedCode: dns.RcodeSuccess, expectedErr: nil, drop: false}, + {rrtype: dns.TypeA, expectedCode: dns.RcodeSuccess, expectedErr: nil, drop: true}, + {rrtype: dns.TypeA, expectedCode: dns.RcodeSuccess, expectedErr: nil, drop: false}, + {rrtype: dns.TypeAAAA, expectedCode: dns.RcodeSuccess, expectedErr: nil, drop: true}, + {rrtype: dns.TypeHINFO, expectedCode: dns.RcodeServerFailure, expectedErr: nil, drop: false}, } ctx := context.TODO() for i, tc := range tests { req := new(dns.Msg) - req.SetQuestion("example.org.", dns.TypeA) + req.SetQuestion("example.org.", tc.rrtype) rec := dnstest.NewRecorder(&test.ResponseWriter{}) code, err := e.ServeDNS(ctx, rec, req) @@ -77,3 +80,21 @@ func TestErraticTruncate(t *testing.T) { } } } + +func TestAxfr(t *testing.T) { + e := &Erratic{truncate: 0} // nothing, just check if we can get an axfr + + ctx := context.TODO() + + req := new(dns.Msg) + req.SetQuestion("example.org.", dns.TypeAXFR) + + rec := dnstest.NewRecorder(&test.ResponseWriter{}) + _, err := e.ServeDNS(ctx, rec, req) + if err != nil { + t.Errorf("Failed to set up AXFR: %s", err) + } + if x := rec.Msg.Answer[0].Header().Rrtype; x != dns.TypeSOA { + t.Errorf("Expected for record to be %d, got %d", dns.TypeSOA, x) + } +} diff --git a/plugin/erratic/xfr.go b/plugin/erratic/xfr.go new file mode 100644 index 000000000..8e2b31794 --- /dev/null +++ b/plugin/erratic/xfr.go @@ -0,0 +1,52 @@ +package erratic + +import ( + "strings" + + "github.com/coredns/coredns/plugin/test" + "github.com/coredns/coredns/request" + + "github.com/miekg/dns" +) + +// allRecords returns a small zone file. The first RR must be a SOA. +func allRecords(name string) []dns.RR { + var rrs = []dns.RR{ + test.SOA("xx. 0 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2018050825 7200 3600 1209600 3600"), + test.NS("xx. 0 IN NS b.xx."), + test.NS("xx. 0 IN NS a.xx."), + test.AAAA("a.xx. 0 IN AAAA 2001:bd8::53"), + test.AAAA("b.xx. 0 IN AAAA 2001:500::54"), + } + + for _, r := range rrs { + r.Header().Name = strings.Replace(r.Header().Name, "xx.", name, 1) + + if n, ok := r.(*dns.NS); ok { + n.Ns = strings.Replace(n.Ns, "xx.", name, 1) + } + } + return rrs +} + +func xfr(state request.Request, truncate bool) { + rrs := allRecords(state.QName()) + + ch := make(chan *dns.Envelope) + tr := new(dns.Transfer) + + go func() { + // So the rrs we have don't have a closing SOA, only add that when truncate is false, + // so we send an incomplete AXFR. + if !truncate { + rrs = append(rrs, rrs[0]) + } + + ch <- &dns.Envelope{RR: rrs} + close(ch) + }() + + tr.Out(state.W, state.Req, ch) + state.W.Hijack() + return +}