From f1f0955cb990e629352440afc8cd9f1546970bde Mon Sep 17 00:00:00 2001 From: Shiv Tyagi <67995771+shiv-tyagi@users.noreply.github.com> Date: Thu, 22 Jan 2026 08:05:48 +0530 Subject: [PATCH] fix: return SOA and NS records when queried for a record CNAMEd to origin (#7808) * fix: return SOA and NS records when queried for a record CNAMEd to origin Signed-off-by: Shiv Tyagi * chore(test): add test for covering cname to origin scenario in file plugin Signed-off-by: Shiv Tyagi --------- Signed-off-by: Shiv Tyagi --- plugin/file/lookup.go | 4 +-- test/example_test.go | 1 + test/file_upstream_test.go | 61 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/plugin/file/lookup.go b/plugin/file/lookup.go index 7e1c6e8fb..d2c57cab5 100644 --- a/plugin/file/lookup.go +++ b/plugin/file/lookup.go @@ -331,7 +331,7 @@ func (z *Zone) externalLookup(ctx context.Context, state request.Request, elem * targetName := rrs[0].(*dns.CNAME).Target elem, _ = z.Search(targetName) - if elem == nil { + if elem == nil || (qtype == dns.TypeNS || qtype == dns.TypeSOA && targetName == z.origin) { lookupRRs, result := z.doLookup(ctx, state, targetName, qtype) rrs = append(rrs, lookupRRs...) return rrs, z.ns(do), nil, result @@ -351,7 +351,7 @@ Redo: } targetName := cname[0].(*dns.CNAME).Target elem, _ = z.Search(targetName) - if elem == nil { + if elem == nil || (qtype == dns.TypeNS || qtype == dns.TypeSOA && targetName == z.origin) { lookupRRs, result := z.doLookup(ctx, state, targetName, qtype) rrs = append(rrs, lookupRRs...) return rrs, z.ns(do), nil, result diff --git a/test/example_test.go b/test/example_test.go index ed1f9c7c3..59c4021c3 100644 --- a/test/example_test.go +++ b/test/example_test.go @@ -12,5 +12,6 @@ short 1 IN A 127.0.0.3 *.w 3600 IN TXT "Wildcard" a.b.c.w IN TXT "Not a wildcard" cname IN CNAME h.gtld-servers.net. +cname-origin IN CNAME @ service IN SRV 8080 10 10 @ ` diff --git a/test/file_upstream_test.go b/test/file_upstream_test.go index 77ffa1d6e..9fbdb5c60 100644 --- a/test/file_upstream_test.go +++ b/test/file_upstream_test.go @@ -227,3 +227,64 @@ www 3600 IN A 127.0.0.53 t.Errorf("Failed to get address for CNAME, expected 127.0.0.53, got %s", x) } } + +// TestFileCnameOrigin tests that CNAMEs pointing to origin are correctly resolved +func TestFileCnameOrigin(t *testing.T) { + cases := map[string]uint16{ + "A": dns.TypeA, + "SOA": dns.TypeSOA, + "NS": dns.TypeNS, + } + + name, rm, err := test.TempFile(".", exampleOrg) + if err != nil { + t.Fatalf("Failed to create zone: %s", err) + } + defer rm() + + // Corefile with for example without proxy section. + corefile := `example.org:0 { + file ` + name + ` + }` + i, udp, _, err := CoreDNSServerAndPorts(corefile) + if err != nil { + t.Fatalf("Could not get CoreDNS serving instance: %s", err) + } + defer i.Stop() + + for name, qtype := range cases { + t.Run(name, func(t *testing.T) { + m := new(dns.Msg) + m.SetQuestion("cname-origin.example.org.", qtype) + + resp, err := dns.Exchange(m, udp) + if err != nil { + t.Fatalf("Expected to receive reply, but didn't: %s", err) + } + + // Check minimum answer count (CNAME + target record) + if len(resp.Answer) < 2 { + t.Fatalf("Expected at least 2 RRs in answer section, got %d", len(resp.Answer)) + } + + // Verify first record is CNAME + if len(resp.Answer) > 0 && resp.Answer[0].Header().Rrtype != dns.TypeCNAME { + t.Errorf("Expected first RR to be CNAME, got %s", dns.TypeToString[resp.Answer[0].Header().Rrtype]) + } + + // Verify we have the queried type in the response + if len(resp.Answer) > 1 { + found := false + for _, rr := range resp.Answer[1:] { + if rr.Header().Rrtype == qtype { + found = true + break + } + } + if !found { + t.Errorf("Expected to find %s record in answer section", dns.TypeToString[qtype]) + } + } + }) + } +}