diff options
Diffstat (limited to 'plugin')
-rw-r--r-- | plugin/template/README.md | 4 | ||||
-rw-r--r-- | plugin/template/setup.go | 17 | ||||
-rw-r--r-- | plugin/template/setup_test.go | 24 | ||||
-rw-r--r-- | plugin/template/template.go | 12 | ||||
-rw-r--r-- | plugin/template/template_test.go | 37 |
5 files changed, 94 insertions, 0 deletions
diff --git a/plugin/template/README.md b/plugin/template/README.md index 5e14ae24a..1bca90662 100644 --- a/plugin/template/README.md +++ b/plugin/template/README.md @@ -17,6 +17,7 @@ template CLASS TYPE [ZONE...] { additional RR authority RR rcode CODE + ederror EXTENDED_ERROR_CODE [EXTRA_REASON] fallthrough [FALLTHROUGH-ZONE...] } ~~~ @@ -31,6 +32,8 @@ template CLASS TYPE [ZONE...] { in a response with an empty answer section. * `rcode` **CODE** A response code (`NXDOMAIN, SERVFAIL, ...`). The default is `NOERROR`. Valid response code values are per the `RcodeToString` map defined by the `miekg/dns` package in `msg.go`. +* `ederror` **EXTENDED_ERROR_CODE** is an extended DNS error code as a number defined in `RFC8914` (0, 1, 2,..., 24). + **EXTRA_REASON** is an additional string explaining the reason for returning the error. * `fallthrough` Continue with the next _template_ instance if the _template_'s **ZONE** matches a query name but no regex match. If there is no next _template_, continue resolution with the next plugin. If **[FALLTHROUGH-ZONE...]** are listed (for example `in-addr.arpa` and `ip6.arpa`), then only queries for those zones will be subject to fallthrough. Without @@ -104,6 +107,7 @@ The `.invalid` domain is a reserved TLD (see [RFC 2606 Reserved Top Level DNS Na template ANY ANY invalid { rcode NXDOMAIN authority "invalid. 60 {{ .Class }} SOA ns.invalid. hostmaster.invalid. (1 60 60 60 60)" + ederror 21 "Blocked according to RFC2606" } } ~~~ diff --git a/plugin/template/setup.go b/plugin/template/setup.go index fc6b75165..56058f032 100644 --- a/plugin/template/setup.go +++ b/plugin/template/setup.go @@ -2,6 +2,7 @@ package template import ( "regexp" + "strconv" gotmpl "text/template" "github.com/coredns/caddy" @@ -123,6 +124,22 @@ func templateParse(c *caddy.Controller) (handler Handler, err error) { } t.rcode = rcode + case "ederror": + args := c.RemainingArgs() + if len(args) != 1 && len(args) != 2 { + return handler, c.ArgErr() + } + + code, err := strconv.ParseUint(args[0], 10, 16) + if err != nil { + return handler, c.Errf("error parsing extended DNS error code %s, %v\n", c.Val(), err) + } + if len(args) == 2 { + t.ederror = &ederror{code: uint16(code), reason: args[1]} + } else { + t.ederror = &ederror{code: uint16(code)} + } + case "fallthrough": t.fall.SetZonesFromArgs(c.RemainingArgs()) diff --git a/plugin/template/setup_test.go b/plugin/template/setup_test.go index 9f03eebcc..345525da6 100644 --- a/plugin/template/setup_test.go +++ b/plugin/template/setup_test.go @@ -161,6 +161,30 @@ func TestSetupParse(t *testing.T) { }`, false, }, + { + `template ANY ANY invalid { + rcode NXDOMAIN + authority "invalid. 60 {{ .Class }} SOA ns.invalid. hostmaster.invalid. (1 60 60 60 60)" + ederror 21 "Blocked according to RFC2606" + }`, + false, + }, + { + `template ANY ANY invalid { + rcode NXDOMAIN + authority "invalid. 60 {{ .Class }} SOA ns.invalid. hostmaster.invalid. (1 60 60 60 60)" + ederror invalid "Blocked according to RFC2606" + }`, + true, + }, + { + `template ANY ANY invalid { + rcode NXDOMAIN + authority "invalid. 60 {{ .Class }} SOA ns.invalid. hostmaster.invalid. (1 60 60 60 60)" + ederror too many arguments + }`, + true, + }, } for i, test := range tests { c := caddy.NewTestController("dns", test.inputFileRules) diff --git a/plugin/template/template.go b/plugin/template/template.go index aee1e1b80..5eac81e8e 100644 --- a/plugin/template/template.go +++ b/plugin/template/template.go @@ -33,10 +33,16 @@ type template struct { authority []*gotmpl.Template qclass uint16 qtype uint16 + ederror *ederror fall fall.F upstream Upstreamer } +type ederror struct { + code uint16 + reason string +} + // Upstreamer looks up targets of CNAME templates type Upstreamer interface { Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error) @@ -125,6 +131,12 @@ func (h Handler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) msg.Ns = append(msg.Ns, rr) } + if template.ederror != nil { + msg = msg.SetEdns0(4096, true) + ede := dns.EDNS0_EDE{InfoCode: template.ederror.code, ExtraText: template.ederror.reason} + msg.IsEdns0().Option = append(msg.IsEdns0().Option, &ede) + } + w.WriteMsg(msg) return template.rcode, nil } diff --git a/plugin/template/template_test.go b/plugin/template/template_test.go index c94deb81f..4c160988d 100644 --- a/plugin/template/template_test.go +++ b/plugin/template/template_test.go @@ -143,6 +143,16 @@ func TestHandler(t *testing.T) { fall: fall.Root, zones: []string{"."}, } + templateWithEDE := template{ + rcode: dns.RcodeNameError, + regex: []*regexp.Regexp{regexp.MustCompile(".*")}, + authority: []*gotmpl.Template{gotmpl.Must(newTemplate("authority", "invalid. 60 {{ .Class }} SOA ns.invalid. hostmaster.invalid. (1 60 60 60 60)"))}, + qclass: dns.ClassANY, + qtype: dns.TypeANY, + fall: fall.Root, + zones: []string{"."}, + ederror: &ederror{code: 21, reason: "Blocked due to RFC2606"}, + } tests := []struct { tmpl template @@ -442,6 +452,33 @@ func TestHandler(t *testing.T) { "foo": "myfoo", }, }, + { + name: "EDNS error", + tmpl: templateWithEDE, + qclass: dns.ClassINET, + qtype: dns.TypeA, + qname: "test.invalid.", + expectedCode: dns.RcodeNameError, + verifyResponse: func(r *dns.Msg) error { + if opt := r.IsEdns0(); opt != nil { + matched := false + for _, ednsopt := range opt.Option { + if ede, ok := ednsopt.(*dns.EDNS0_EDE); ok { + if ede.InfoCode != dns.ExtendedErrorCodeNotSupported { + return fmt.Errorf("unexpected EDE code = %v, want %v", ede.InfoCode, dns.ExtendedErrorCodeNotSupported) + } + matched = true + } + } + if !matched { + t.Error("Error: acl.ServeDNS() missing Extended DNS Error option") + } + } else { + return fmt.Errorf("expected EDNS enabled") + } + return nil + }, + }, } ctx := context.TODO() |