diff --git a/plugin/file/lookup.go b/plugin/file/lookup.go index 6dc2f9b04..a187ae37f 100644 --- a/plugin/file/lookup.go +++ b/plugin/file/lookup.go @@ -54,10 +54,10 @@ func (z *Zone) Lookup(ctx context.Context, state request.Request, qname string) } var ( - found, shot bool - parts string - i int - elem, wildElem *tree.Elem + found, shot bool + parts, wildName string + i int + elem, wildElem *tree.Elem ) 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) 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,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 // 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()) { - rcode = Success - } + if hasDescendant(tr, qname) { + rcode = Success } ret := ap.soa(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) diff --git a/plugin/file/wildcard_test.go b/plugin/file/wildcard_test.go index c57484edf..e6c404360 100644 --- a/plugin/file/wildcard_test.go +++ b/plugin/file/wildcard_test.go @@ -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) + } +}