plugin/file: resolve each additional section target only once (#8286)

* plugin/file: resolve each additional section target only once

additionalProcessing walked the answer and appended each target's address records without keeping track of the targets it had already resolved. An answer holding several records that point at one target, two MX records differing only in preference for instance, therefore repeated that target's A, AAAA and RRSIGs once per record. SRV, SVCB and HTTPS take the same path and behaved the same way. plugin/backend_lookup.go already skips targets it has seen; the file plugin never did.

Targets are compared in canonical form because SRV targets are not lowercased on insert, so one target can reach additional processing spelled two ways while the zone's tree still matches it case-insensitively. The map is allocated on first use, as additionalProcessing runs for every answer and most carry no target at all.

Fixes #6626

Signed-off-by: baltasarblanco <baltablanco9008@gmail.com>

* plugin/file: move additional section tests to lookup_test.go

The cases exercise additionalProcessing, which lives in lookup.go, so they belong with the rest of the lookup tests. No test logic is changed.

Addresses review feedback.

Signed-off-by: baltasarblanco <baltablanco9008@gmail.com>

---------

Signed-off-by: baltasarblanco <baltablanco9008@gmail.com>
This commit is contained in:
Baltasar Blanco
2026-07-24 19:10:04 -03:00
committed by GitHub
parent 73d1eacf87
commit 989bf4a9fd
2 changed files with 163 additions and 1 deletions

View File

@@ -434,8 +434,11 @@ func (z *Zone) doLookup(ctx context.Context, state request.Request, target strin
}
// additionalProcessing checks the current answer section and retrieves A or AAAA records
// (and possible SIGs) to need to be put in the additional section.
// (and possible SIGs) to need to be put in the additional section. A target referenced by
// more than one record is only resolved once.
func (z *Zone) additionalProcessing(answer []dns.RR, do bool) (extra []dns.RR) {
var lookup map[string]struct{}
for _, rr := range answer {
name := ""
switch x := rr.(type) {
@@ -452,6 +455,20 @@ func (z *Zone) additionalProcessing(answer []dns.RR, do bool) (extra []dns.RR) {
continue
}
// The answer can reference one target more than once, e.g. two MX records that only
// differ in preference. Its addresses belong in the additional section once. Compare
// canonically: SRV targets are not lowercased on insert (see Zone.Insert), while the
// zone's tree matches names case-insensitively.
target := dns.CanonicalName(name)
if _, ok := lookup[target]; ok {
continue
}
if lookup == nil {
// Allocate on first use: this runs for every answer, most of which carry no target.
lookup = make(map[string]struct{}, len(answer))
}
lookup[target] = struct{}{}
elem, _ := z.Search(name)
if elem == nil {
continue