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:
@@ -55,7 +55,7 @@ func (z *Zone) Lookup(ctx context.Context, state request.Request, qname string)
|
||||
|
||||
var (
|
||||
found, shot bool
|
||||
parts string
|
||||
parts, wildName string
|
||||
i int
|
||||
elem, wildElem *tree.Elem
|
||||
)
|
||||
@@ -99,6 +99,11 @@ func (z *Zone) Lookup(ctx context.Context, state request.Request, qname string)
|
||||
wildcard := replaceWithAsteriskLabel(parts)
|
||||
if wild, found := tr.Search(wildcard); found {
|
||||
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
|
||||
@@ -188,8 +193,12 @@ func (z *Zone) Lookup(ctx context.Context, state request.Request, qname string)
|
||||
|
||||
// Haven't found the original name.
|
||||
|
||||
// Found wildcard.
|
||||
if wildElem != nil && !closerENTExists(tr, qname, wildElem.Name()) {
|
||||
// Found a wildcard source of synthesis. It may be an empty non-terminal.
|
||||
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
|
||||
metadata.SetValueFunc(ctx, "zone/wildcard", func() string {
|
||||
return wildElem.Name()
|
||||
@@ -236,11 +245,9 @@ 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
|
||||
// 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 dns.IsSubDomain(qname, x.Name()) {
|
||||
if hasDescendant(tr, qname) {
|
||||
rcode = Success
|
||||
}
|
||||
}
|
||||
|
||||
ret := ap.soa(do)
|
||||
if do {
|
||||
@@ -292,8 +299,7 @@ func closerENTExists(tr *tree.Tree, qname, wildcardName string) bool {
|
||||
if name == parent || !dns.IsSubDomain(parent, name) {
|
||||
return false
|
||||
}
|
||||
// An ENT exists at `name` iff tr.Next(name) returns a descendant of name.
|
||||
if x, found := tr.Next(name); found && dns.IsSubDomain(name, x.Name()) {
|
||||
if hasDescendant(tr, name) {
|
||||
return true
|
||||
}
|
||||
offset, end = dns.NextLabel(name, 0)
|
||||
@@ -301,6 +307,12 @@ func closerENTExists(tr *tree.Tree, qname, wildcardName string) bool {
|
||||
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.
|
||||
func typeFromElem(elem *tree.Elem, tp uint16, do bool) []dns.RR {
|
||||
rrs := elem.Type(tp)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
||||
"github.com/coredns/coredns/plugin/test"
|
||||
"github.com/coredns/coredns/request"
|
||||
|
||||
"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