Files
coredns/plugin/pkg/upstream/upstream_test.go
Ville Vesilehto 4ec856f347 Merge commit from fork
EndpointSlice FQDN addresses were copied into endpoint records as if
they were IP addresses. CoreDNS then treated each value as a CNAME
target. A tenant could build a branching chain of headless Services that
made one query perform exponential work and exhaust CoreDNS memory.

Publish endpoints only from IPv4 and IPv6 slices and validate every
address against its declared family. Deduplicate CNAME targets for the
whole query so repeated or converging paths are chased once.

Protect internal self-lookups with an independent depth limit. This
stops direct and indirect k8s_external CNAME loops without consuming the
loop counter already maintained by the file and rewrite plugins.

Add regression coverage for address types, recursive convergence,
repeated k8s_external targets, and direct and indirect self-reference.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
2026-10-24 19:45:58 -07:00

102 lines
3.4 KiB
Go

package upstream
import (
"context"
"errors"
"testing"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// selfReferencing answers every query with a CNAME to the query name itself
// and then asks the upstream for that target, the way a backend does for a
// CNAME-typed service whose target leads back to the backend.
type selfReferencing struct {
up *Upstream
calls int
}
func (s *selfReferencing) Name() string { return "selfref" }
func (s *selfReferencing) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
s.calls++
state := request.Request{W: w, Req: r}
m := new(dns.Msg)
m.SetReply(r)
m.Answer = []dns.RR{test.CNAME(state.QName() + " 5 IN CNAME " + state.QName())}
if resp, err := s.up.Lookup(ctx, state, state.QName(), state.QType()); err == nil && resp != nil {
m.Answer = append(m.Answer, resp.Answer...)
}
w.WriteMsg(m)
return dns.RcodeSuccess, nil
}
func TestLookupDepthIsBounded(t *testing.T) {
h := &selfReferencing{up: New()}
cfg := &dnsserver.Config{Zone: ".", Plugin: []plugin.Plugin{func(plugin.Handler) plugin.Handler { return h }}}
server, err := dnsserver.NewServer("dns://:53", []*dnsserver.Config{cfg})
if err != nil {
t.Fatalf("NewServer: %v", err)
}
ctx := context.WithValue(context.Background(), dnsserver.Key{}, server)
m := new(dns.Msg)
m.SetQuestion("x.example.org.", dns.TypeA)
rec := &test.ResponseWriter{}
server.ServeDNS(ctx, rec, m)
// The client query plus maxDepth nested lookups.
if want := maxDepth + 1; h.calls != want {
t.Fatalf("expected %d invocations of the plugin chain, got %d", want, h.calls)
}
}
func TestLookupReturnsErrMaxDepth(t *testing.T) {
server, err := dnsserver.NewServer("dns://:53", []*dnsserver.Config{{Zone: "."}})
if err != nil {
t.Fatalf("NewServer: %v", err)
}
ctx := context.WithValue(context.Background(), dnsserver.Key{}, server)
ctx = context.WithValue(ctx, depthKey{}, maxDepth)
m := new(dns.Msg)
m.SetQuestion("x.example.org.", dns.TypeA)
state := request.Request{W: &test.ResponseWriter{}, Req: m}
if _, err := New().Lookup(ctx, state, "y.example.org.", dns.TypeA); !errors.Is(err, errMaxDepth) {
t.Fatalf("expected errMaxDepth, got %v", err)
}
}
func TestLookupDoesNotConsumeServerLoopDepth(t *testing.T) {
h := &selfReferencing{up: New()}
cfg := &dnsserver.Config{Zone: ".", Plugin: []plugin.Plugin{func(plugin.Handler) plugin.Handler { return h }}}
server, err := dnsserver.NewServer("dns://:53", []*dnsserver.Config{cfg})
if err != nil {
t.Fatalf("NewServer: %v", err)
}
ctx := context.WithValue(context.Background(), dnsserver.Key{}, server)
ctx = context.WithValue(ctx, dnsserver.LoopKey{}, maxDepth)
m := new(dns.Msg)
m.SetQuestion("x.example.org.", dns.TypeA)
state := request.Request{W: &test.ResponseWriter{}, Req: m}
if _, err := New().Lookup(ctx, state, "y.example.org.", dns.TypeA); err != nil {
t.Fatalf("Lookup unexpectedly used dnsserver.LoopKey: %v", err)
}
}
func TestLookupWithoutServer(t *testing.T) {
m := new(dns.Msg)
m.SetQuestion("x.example.org.", dns.TypeA)
state := request.Request{W: &test.ResponseWriter{}, Req: m}
if _, err := New().Lookup(context.Background(), state, "y.example.org.", dns.TypeA); err == nil {
t.Fatal("expected an error without a server in the context")
}
}