mirror of
https://github.com/coredns/coredns.git
synced 2026-08-20 23:08:28 -04:00
plugin/file: handle empty non-terminal wildcard sources (#8386)
RFC 4592 permits a wildcard source of synthesis to exist as an empty non-terminal. Track wildcard names proven by descendant records and return NODATA when such a source is selected. Fixes #4256 Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>
This commit is contained in:
@@ -54,10 +54,10 @@ func (z *Zone) Lookup(ctx context.Context, state request.Request, qname string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
found, shot bool
|
found, shot bool
|
||||||
parts string
|
parts, wildName string
|
||||||
i int
|
i int
|
||||||
elem, wildElem *tree.Elem
|
elem, wildElem *tree.Elem
|
||||||
)
|
)
|
||||||
|
|
||||||
loop, _ := ctx.Value(dnsserver.LoopKey{}).(int)
|
loop, _ := ctx.Value(dnsserver.LoopKey{}).(int)
|
||||||
@@ -99,6 +99,11 @@ func (z *Zone) Lookup(ctx context.Context, state request.Request, qname string)
|
|||||||
wildcard := replaceWithAsteriskLabel(parts)
|
wildcard := replaceWithAsteriskLabel(parts)
|
||||||
if wild, found := tr.Search(wildcard); found {
|
if wild, found := tr.Search(wildcard); found {
|
||||||
wildElem = wild
|
wildElem = wild
|
||||||
|
wildName = wild.Name()
|
||||||
|
} else if hasDescendant(tr, wildcard) {
|
||||||
|
// A wildcard domain may exist as an empty non-terminal.
|
||||||
|
wildElem = nil
|
||||||
|
wildName = wildcard
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep on searching, because maybe we hit an empty-non-terminal (which aren't
|
// Keep on searching, because maybe we hit an empty-non-terminal (which aren't
|
||||||
@@ -188,8 +193,12 @@ func (z *Zone) Lookup(ctx context.Context, state request.Request, qname string)
|
|||||||
|
|
||||||
// Haven't found the original name.
|
// Haven't found the original name.
|
||||||
|
|
||||||
// Found wildcard.
|
// Found a wildcard source of synthesis. It may be an empty non-terminal.
|
||||||
if wildElem != nil && !closerENTExists(tr, qname, wildElem.Name()) {
|
if wildName != "" && !closerENTExists(tr, qname, wildName) {
|
||||||
|
if wildElem == nil {
|
||||||
|
return nil, ap.soa(do), nil, NoData
|
||||||
|
}
|
||||||
|
|
||||||
// set metadata value for the wildcard record that synthesized the result
|
// set metadata value for the wildcard record that synthesized the result
|
||||||
metadata.SetValueFunc(ctx, "zone/wildcard", func() string {
|
metadata.SetValueFunc(ctx, "zone/wildcard", func() string {
|
||||||
return wildElem.Name()
|
return wildElem.Name()
|
||||||
@@ -236,10 +245,8 @@ func (z *Zone) Lookup(ctx context.Context, state request.Request, qname string)
|
|||||||
|
|
||||||
// Hacky way to get around empty-non-terminals. If a longer name does exist, but this qname, does not, it
|
// Hacky way to get around empty-non-terminals. If a longer name does exist, but this qname, does not, it
|
||||||
// must be an empty-non-terminal. If so, we do the proper NXDOMAIN handling, but set the rcode to be success.
|
// must be an empty-non-terminal. If so, we do the proper NXDOMAIN handling, but set the rcode to be success.
|
||||||
if x, found := tr.Next(qname); found {
|
if hasDescendant(tr, qname) {
|
||||||
if dns.IsSubDomain(qname, x.Name()) {
|
rcode = Success
|
||||||
rcode = Success
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := ap.soa(do)
|
ret := ap.soa(do)
|
||||||
@@ -292,8 +299,7 @@ func closerENTExists(tr *tree.Tree, qname, wildcardName string) bool {
|
|||||||
if name == parent || !dns.IsSubDomain(parent, name) {
|
if name == parent || !dns.IsSubDomain(parent, name) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// An ENT exists at `name` iff tr.Next(name) returns a descendant of name.
|
if hasDescendant(tr, name) {
|
||||||
if x, found := tr.Next(name); found && dns.IsSubDomain(name, x.Name()) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
offset, end = dns.NextLabel(name, 0)
|
offset, end = dns.NextLabel(name, 0)
|
||||||
@@ -301,6 +307,12 @@ func closerENTExists(tr *tree.Tree, qname, wildcardName string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasDescendant reports whether the tree contains a name strictly below name.
|
||||||
|
func hasDescendant(tr *tree.Tree, name string) bool {
|
||||||
|
x, found := tr.Next(name)
|
||||||
|
return found && tree.Less(x, name) != 0 && dns.IsSubDomain(name, x.Name())
|
||||||
|
}
|
||||||
|
|
||||||
// typeFromElem returns the type tp from e and adds signatures (if they exist) and do is true.
|
// typeFromElem returns the type tp from e and adds signatures (if they exist) and do is true.
|
||||||
func typeFromElem(elem *tree.Elem, tp uint16, do bool) []dns.RR {
|
func typeFromElem(elem *tree.Elem, tp uint16, do bool) []dns.RR {
|
||||||
rrs := elem.Type(tp)
|
rrs := elem.Type(tp)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
||||||
"github.com/coredns/coredns/plugin/test"
|
"github.com/coredns/coredns/plugin/test"
|
||||||
|
"github.com/coredns/coredns/request"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -410,3 +411,93 @@ func TestLookupWildcardRespectsCloserEmptyNonTerminal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emptyNonTerminalWildcard = `; example.org test file with an empty non-terminal wildcard
|
||||||
|
$TTL 3600
|
||||||
|
example.org. IN SOA ns.example.org. hostmaster.example.org. 1 7200 3600 1209600 3600
|
||||||
|
example.org. IN NS ns.example.org.
|
||||||
|
f.*.e.example.org. IN A 192.0.2.1
|
||||||
|
t.e.example.org. IN A 192.0.2.2
|
||||||
|
real.deep.e.example.org. IN A 192.0.2.3
|
||||||
|
*.ordinary.example.org. IN A 192.0.2.4
|
||||||
|
`
|
||||||
|
|
||||||
|
var emptyNonTerminalWildcardTestCases = []test.Case{
|
||||||
|
{
|
||||||
|
Qname: "something.e.example.org.", Qtype: dns.TypeA,
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.SOA(`example.org. 3600 IN SOA ns.example.org. hostmaster.example.org. 1 7200 3600 1209600 3600`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "something.e.example.org.", Qtype: dns.TypeAAAA,
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.SOA(`example.org. 3600 IN SOA ns.example.org. hostmaster.example.org. 1 7200 3600 1209600 3600`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "something.somewhere.e.example.org.", Qtype: dns.TypeA,
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.SOA(`example.org. 3600 IN SOA ns.example.org. hostmaster.example.org. 1 7200 3600 1209600 3600`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "f.*.e.example.org.",
|
||||||
|
Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{test.A(`f.*.e.example.org. 3600 IN A 192.0.2.1`)},
|
||||||
|
Ns: []dns.RR{test.NS(`example.org. 3600 IN NS ns.example.org.`)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// *.e.example.org. is the closest encloser, not a source of synthesis for its own descendants.
|
||||||
|
Qname: "missing.*.e.example.org.", Qtype: dns.TypeA,
|
||||||
|
Rcode: dns.RcodeNameError,
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.SOA(`example.org. 3600 IN SOA ns.example.org. hostmaster.example.org. 1 7200 3600 1209600 3600`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// deep.e.example.org. is a closer empty non-terminal, so *.e.example.org. does not apply.
|
||||||
|
Qname: "missing.deep.e.example.org.", Qtype: dns.TypeA,
|
||||||
|
Rcode: dns.RcodeNameError,
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.SOA(`example.org. 3600 IN SOA ns.example.org. hostmaster.example.org. 1 7200 3600 1209600 3600`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "something.ordinary.example.org.",
|
||||||
|
Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{test.A(`something.ordinary.example.org. 3600 IN A 192.0.2.4`)},
|
||||||
|
Ns: []dns.RR{test.NS(`example.org. 3600 IN NS ns.example.org.`)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookupEmptyNonTerminalWildcard(t *testing.T) {
|
||||||
|
const name = "example.org."
|
||||||
|
zone, err := Parse(strings.NewReader(emptyNonTerminalWildcard), name, "stdin", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expect no error when reading zone, got %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{name: zone}, Names: []string{name}}}
|
||||||
|
ctx := context.TODO()
|
||||||
|
|
||||||
|
for _, tc := range emptyNonTerminalWildcardTestCases {
|
||||||
|
m := tc.Msg()
|
||||||
|
|
||||||
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
||||||
|
if _, err := fm.ServeDNS(ctx, rec, m); err != nil {
|
||||||
|
t.Errorf("Expected no error for %q/%d, got %v", tc.Qname, tc.Qtype, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := test.SortAndCheck(rec.Msg, tc); err != nil {
|
||||||
|
t.Errorf("Test %q/%d: %v", tc.Qname, tc.Qtype, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tc := emptyNonTerminalWildcardTestCases[0]
|
||||||
|
state := request.Request{W: &test.ResponseWriter{}, Req: tc.Msg()}
|
||||||
|
if _, _, _, result := zone.Lookup(ctx, state, tc.Qname); result != NoData {
|
||||||
|
t.Errorf("Expected NoData result for empty non-terminal wildcard, got %v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user