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 +}