plugin/file: test that out-of-zone CNAME targets keep NOERROR (#8569)

This commit is contained in:
Baltasar Blanco
2026-10-16 18:05:50 -03:00
committed by GitHub
parent 16b53a46c1
commit 62be0a2f90
3 changed files with 67 additions and 2 deletions

View File

@@ -469,6 +469,9 @@ func delegationFromElem(tr *tree.Tree, elem *tree.Elem, qname string, qtype uint
} }
func (z *Zone) doLookup(ctx context.Context, state request.Request, target string, qtype uint16) ([]dns.RR, Result) { func (z *Zone) doLookup(ctx context.Context, state request.Request, target string, qtype uint16) ([]dns.RR, Result) {
if z.Upstream == nil {
return nil, ServerFailure
}
m, e := z.Upstream.Lookup(ctx, state, target, qtype) m, e := z.Upstream.Lookup(ctx, state, target, qtype)
if e != nil { if e != nil {
return nil, ServerFailure return nil, ServerFailure
@@ -485,6 +488,9 @@ func (z *Zone) doLookup(ctx context.Context, state request.Request, target strin
if m.Rcode == dns.RcodeSuccess && len(m.Answer) == 0 { if m.Rcode == dns.RcodeSuccess && len(m.Answer) == 0 {
return m.Answer, NoData return m.Answer, NoData
} }
// Anything else, including the REFUSED returned for a target outside every
// zone served here, keeps NOERROR: the CNAME is the answer and the resolver
// chases the target itself. See #7346.
return m.Answer, Success return m.Answer, Success
} }

View File

@@ -557,3 +557,55 @@ cname-srv IN CNAME srv.example.org.
dname IN DNAME dsub.example.org. dname IN DNAME dsub.example.org.
srv.dsub IN SRV 10 50 8080 host.example.org. srv.dsub IN SRV 10 50 8080 host.example.org.
` `
const dbExampleOrgOutOfZone = `$ORIGIN example.org.
@ 3600 IN SOA sns.dns.icann.org. noc.dns.icann.org. ( 2017042745 7200 3600 1209600 3600 )
3600 IN NS a.iana-servers.net.
www 3600 IN CNAME www.example.net.
`
// refusedUpstream answers every lookup with REFUSED, which is what the server
// returns for a name that matches none of the zones it serves.
type refusedUpstream struct{}
func (refusedUpstream) Lookup(_ context.Context, _ request.Request, name string, typ uint16) (*dns.Msg, error) {
m := new(dns.Msg)
m.SetQuestion(name, typ)
m.Rcode = dns.RcodeRefused
return m, nil
}
// TestLookupRefusedUpstream checks that a CNAME whose target lies outside every
// served zone is answered with NOERROR and the CNAME, so the resolver can chase
// the target itself. The self-lookup for such a target comes back REFUSED, which
// is not an error for an authoritative answer. See #7346 and #7381.
func TestLookupRefusedUpstream(t *testing.T) {
const origin = "example.org."
zone, err := Parse(strings.NewReader(dbExampleOrgOutOfZone), origin, "stdin", 0)
if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err)
}
zone.Upstream = refusedUpstream{}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{origin: zone}, Names: []string{origin}}}
m := new(dns.Msg)
m.SetQuestion("www.example.org.", dns.TypeA)
rec := dnstest.NewRecorder(&test.ResponseWriter{})
if _, err := fm.ServeDNS(context.TODO(), rec, m); err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if rec.Msg == nil {
t.Fatal("Expected a response to be written, got none")
}
if rec.Msg.Rcode != dns.RcodeSuccess {
t.Errorf("rcode is %q, expected %q", dns.RcodeToString[rec.Msg.Rcode], dns.RcodeToString[dns.RcodeSuccess])
}
if len(rec.Msg.Answer) != 1 {
t.Fatalf("Expected the CNAME in the answer, got %d records", len(rec.Msg.Answer))
}
if _, ok := rec.Msg.Answer[0].(*dns.CNAME); !ok {
t.Errorf("Expected a CNAME in the answer, got %s", rec.Msg.Answer[0])
}
}

View File

@@ -1,6 +1,7 @@
package file package file
import ( import (
"context"
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -8,11 +9,17 @@ import (
"time" "time"
"github.com/coredns/coredns/plugin/file/tree" "github.com/coredns/coredns/plugin/file/tree"
"github.com/coredns/coredns/plugin/pkg/upstream" "github.com/coredns/coredns/request"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
// Upstreamer is used to resolve CNAME or other external targets during the
// resolution process.
type Upstreamer interface {
Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error)
}
// Zone is a structure that contains all data related to a DNS zone. // Zone is a structure that contains all data related to a DNS zone.
type Zone struct { type Zone struct {
origin string origin string
@@ -32,7 +39,7 @@ type Zone struct {
ReloadByMtime bool ReloadByMtime bool
reloadShutdown chan bool reloadShutdown chan bool
Upstream *upstream.Upstream // Upstream for looking up external names during the resolution process. Upstream Upstreamer // Upstream for looking up external names during the resolution process.
} }
// Apex contains the apex records of a zone: SOA, NS and their potential signatures. // Apex contains the apex records of a zone: SOA, NS and their potential signatures.