mirror of
https://github.com/coredns/coredns.git
synced 2026-10-27 04:35:43 -04:00
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>
This commit is contained in:
@@ -680,3 +680,125 @@ func TestIsDuplicate(t *testing.T) {
|
||||
t.Fatal("expected duplicate on second insert (addr)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCNAMETargetsChasedOnce checks that a backend returning the same CNAME
|
||||
// target many times (one per endpoint address, say) causes a single chase of
|
||||
// that target, both for targets outside the zone (one upstream lookup) and
|
||||
// for targets inside the zone (one recursive backend lookup).
|
||||
func TestCNAMETargetsChasedOnce(t *testing.T) {
|
||||
const fanout = 50
|
||||
for _, qtype := range []uint16{dns.TypeA, dns.TypeAAAA, dns.TypeTXT} {
|
||||
lookups := 0
|
||||
backendCalls := map[string]int{}
|
||||
b := &mockBackend{
|
||||
mockServices: func(_ context.Context, state request.Request, _ bool, _ Options) ([]msg.Service, error) {
|
||||
backendCalls[state.Name()]++
|
||||
switch state.Name() {
|
||||
case "a.example.org.":
|
||||
var svcs []msg.Service
|
||||
for i := 0; i < fanout; i++ {
|
||||
svcs = append(svcs, msg.Service{Host: "b.example.org", Key: "/skydns/org/example/a"})
|
||||
svcs = append(svcs, msg.Service{Host: "ext.example.com", Key: "/skydns/org/example/a"})
|
||||
}
|
||||
return svcs, nil
|
||||
case "b.example.org.":
|
||||
if qtype == dns.TypeTXT {
|
||||
return []msg.Service{{Text: "hello", Key: "/skydns/org/example/b"}}, nil
|
||||
}
|
||||
return []msg.Service{{Host: "10.0.0.1", Key: "/skydns/org/example/b"}, {Host: "2001:db8::1", Key: "/skydns/org/example/b"}}, nil
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
},
|
||||
mockLookup: func(_ context.Context, _ request.Request, name string, _ uint16) (*dns.Msg, error) {
|
||||
lookups++
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(name, qtype)
|
||||
return m, nil
|
||||
},
|
||||
}
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("a.example.org.", qtype)
|
||||
state := request.Request{W: &test.ResponseWriter{}, Req: m, Zone: "example.org."}
|
||||
|
||||
var records []dns.RR
|
||||
var err error
|
||||
switch qtype {
|
||||
case dns.TypeA:
|
||||
records, _, err = A(context.TODO(), b, "example.org.", state, nil, Options{})
|
||||
case dns.TypeAAAA:
|
||||
records, _, err = AAAA(context.TODO(), b, "example.org.", state, nil, Options{})
|
||||
case dns.TypeTXT:
|
||||
records, _, err = TXT(context.TODO(), b, "example.org.", state, nil, Options{})
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%s: unexpected error: %v", dns.TypeToString[qtype], err)
|
||||
}
|
||||
if lookups != 1 {
|
||||
t.Errorf("%s: expected 1 upstream lookup for the repeated external target, got %d", dns.TypeToString[qtype], lookups)
|
||||
}
|
||||
if backendCalls["b.example.org."] != 1 {
|
||||
t.Errorf("%s: expected 1 backend lookup for the repeated internal target, got %d", dns.TypeToString[qtype], backendCalls["b.example.org."])
|
||||
}
|
||||
cnames := 0
|
||||
for _, rr := range records {
|
||||
if rr.Header().Rrtype == dns.TypeCNAME {
|
||||
cnames++
|
||||
}
|
||||
}
|
||||
// One CNAME to b (followed by its data) and one to ext (whose upstream answer is empty here).
|
||||
if cnames != 2 {
|
||||
t.Errorf("%s: expected 2 CNAME records in the answer, got %d: %v", dns.TypeToString[qtype], cnames, records)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCNAMETargetsChasedOnceAcrossRecursion(t *testing.T) {
|
||||
for _, qtype := range []uint16{dns.TypeA, dns.TypeAAAA, dns.TypeTXT} {
|
||||
backendCalls := map[string]int{}
|
||||
b := &mockBackend{
|
||||
mockServices: func(_ context.Context, state request.Request, _ bool, _ Options) ([]msg.Service, error) {
|
||||
backendCalls[state.Name()]++
|
||||
switch state.Name() {
|
||||
case "a.example.org.":
|
||||
return []msg.Service{{Host: "b.example.org"}, {Host: "c.example.org"}}, nil
|
||||
case "b.example.org.", "c.example.org.":
|
||||
return []msg.Service{{Host: "d.example.org"}}, nil
|
||||
case "d.example.org.":
|
||||
switch qtype {
|
||||
case dns.TypeA:
|
||||
return []msg.Service{{Host: "10.0.0.1"}}, nil
|
||||
case dns.TypeAAAA:
|
||||
return []msg.Service{{Host: "2001:db8::1"}}, nil
|
||||
default:
|
||||
return []msg.Service{{Text: "hello"}}, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
},
|
||||
mockLookup: func(_ context.Context, _ request.Request, _ string, _ uint16) (*dns.Msg, error) {
|
||||
return nil, errors.New("unexpected external lookup")
|
||||
},
|
||||
}
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("a.example.org.", qtype)
|
||||
state := request.Request{W: &test.ResponseWriter{}, Req: m, Zone: "example.org."}
|
||||
|
||||
var err error
|
||||
switch qtype {
|
||||
case dns.TypeA:
|
||||
_, _, err = A(context.Background(), b, "example.org.", state, nil, Options{})
|
||||
case dns.TypeAAAA:
|
||||
_, _, err = AAAA(context.Background(), b, "example.org.", state, nil, Options{})
|
||||
case dns.TypeTXT:
|
||||
_, _, err = TXT(context.Background(), b, "example.org.", state, nil, Options{})
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%s: unexpected error: %v", dns.TypeToString[qtype], err)
|
||||
}
|
||||
if backendCalls["d.example.org."] != 1 {
|
||||
t.Errorf("%s: expected converged target to be chased once, got %d", dns.TypeToString[qtype], backendCalls["d.example.org."])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user