plugin/template : add support for extended DNS errors (#5659)

* plugin/template : add support for extended DNS errors

Signed-off-by: Ondřej Benkovský <ondrej.benkovsky@jamf.com>
This commit is contained in:
Ondřej Benkovský
2022-10-03 17:04:56 +02:00
committed by GitHub
parent b9a31f2c89
commit 2fa9821c7e
5 changed files with 94 additions and 0 deletions

View File

@@ -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"
}
}
~~~

View File

@@ -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())

View File

@@ -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)

View File

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

View File

@@ -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()