plugin/file: introduce snapshot()/setData() accessors for zone data (#8040)

Signed-off-by: Ryan Brewster <rpb@anthropic.com>
This commit is contained in:
rpb-ant
2026-04-12 13:34:36 -04:00
committed by GitHub
parent 8a28dc9c7d
commit 50cbaf87a0
5 changed files with 41 additions and 52 deletions

View File

@@ -37,10 +37,7 @@ func (z *Zone) Lookup(ctx context.Context, state request.Request, qname string)
// If z is a secondary zone we might not have transferred it, meaning we have
// all zone context setup, except the actual record. This means (for one thing) the apex
// is empty and we don't have a SOA record.
z.RLock()
ap := z.Apex
tr := z.Tree
z.RUnlock()
ap, tr := z.snapshot()
if ap.SOA == nil {
return nil, nil, nil, ServerFailure
}

View File

@@ -36,13 +36,9 @@ func (z *Zone) Reload(t *transfer.Transfer) error {
continue
}
// copy elements we need
z.Lock()
z.Apex = zone.Apex
z.Tree = zone.Tree
z.Unlock()
z.setData(zone.Apex, zone.Tree)
log.Infof("Successfully reloaded zone %q in %q with %d SOA serial", z.origin, zFile, z.SOA.Serial)
log.Infof("Successfully reloaded zone %q in %q with %d SOA serial", z.origin, zFile, zone.SOA.Serial)
if t != nil {
if err := t.Notify(z.origin); err != nil {
log.Warningf("Failed sending notifies: %s", err)

View File

@@ -53,11 +53,7 @@ Transfer:
return Err
}
z.Lock()
z.Tree = z1.Tree
z.Apex = z1.Apex
z.Expired = false
z.Unlock()
z.setData(z1.Apex, z1.Tree)
log.Infof("Transferred: %s from %s", z.origin, tr)
// Send notify messages to secondary servers
@@ -98,10 +94,10 @@ Transfer:
if serial == -1 {
return false, Err
}
if !z.hasSOA() {
soa := z.getSOA()
if soa == nil {
return true, Err
}
soa := z.getSOA()
return less(soa.Serial, uint32(serial)), Err // #nosec G115 -- serial fits in uint32 per DNS RFC
}
@@ -119,7 +115,7 @@ func less(a, b uint32) bool {
// will be marked expired.
func (z *Zone) Update(updateShutdown chan bool, t *transfer.Transfer) error {
// If we don't have a SOA, we don't have a zone, wait for it to appear.
for !z.hasSOA() {
for z.getSOA() == nil {
time.Sleep(1 * time.Second)
}
retryActive := false

View File

@@ -19,20 +19,16 @@ func (f File) Transfer(zone string, serial uint32) (<-chan []dns.RR, error) {
// Transfer transfers a zone with serial in the returned channel and implements IXFR fallback, by just
// sending a single SOA record.
func (z *Zone) Transfer(serial uint32) (<-chan []dns.RR, error) {
// Snapshot apex and tree under one read lock so the goroutine walks the
// same generation the SOA came from, even if TransferIn swaps them mid-AXFR.
z.RLock()
apex, err := z.apexIfDefinedLocked()
t := z.Tree
z.RUnlock()
ap, t := z.snapshot()
apex, err := ap.records()
if err != nil {
return nil, err
}
ch := make(chan []dns.RR)
go func() {
if serial != 0 && apex[0].(*dns.SOA).Serial == serial { // ixfr fallback, only send SOA
ch <- []dns.RR{apex[0]}
if serial != 0 && ap.SOA.Serial == serial { // ixfr fallback, only send SOA
ch <- []dns.RR{ap.SOA}
close(ch)
return
@@ -40,7 +36,7 @@ func (z *Zone) Transfer(serial uint32) (<-chan []dns.RR, error) {
ch <- apex
t.Walk(func(e *tree.Elem, _ map[uint16][]dns.RR) error { ch <- e.All(); return nil })
ch <- []dns.RR{apex[0]}
ch <- []dns.RR{ap.SOA}
close(ch)
}()

View File

@@ -142,34 +142,44 @@ func (z *Zone) SetFile(path string) {
z.Unlock()
}
// ApexIfDefined returns the apex nodes from z. The SOA record is the first record, if it does not exist, an error is returned.
func (z *Zone) ApexIfDefined() ([]dns.RR, error) {
// snapshot returns the apex and tree under a single read lock so callers see
// a consistent zone generation even if TransferIn or Reload swaps them.
func (z *Zone) snapshot() (Apex, *tree.Tree) {
z.RLock()
defer z.RUnlock()
return z.apexIfDefinedLocked()
return z.Apex, z.Tree
}
// apexIfDefinedLocked is ApexIfDefined without locking; caller must hold z's read lock.
func (z *Zone) apexIfDefinedLocked() ([]dns.RR, error) {
if z.SOA == nil {
// setData atomically replaces the zone's apex and tree and clears the expired
// flag. It is the write-side counterpart to snapshot.
func (z *Zone) setData(ap Apex, t *tree.Tree) {
z.Lock()
z.Apex = ap
z.Tree = t
z.Expired = false
z.Unlock()
}
// records returns the apex records in zone-file order (SOA, RRSIG(SOA), NS,
// RRSIG(NS)), or an error if no SOA is set.
func (a Apex) records() ([]dns.RR, error) {
if a.SOA == nil {
return nil, fmt.Errorf("no SOA")
}
rrs := []dns.RR{z.SOA}
if len(z.SIGSOA) > 0 {
rrs = append(rrs, z.SIGSOA...)
}
if len(z.NS) > 0 {
rrs = append(rrs, z.NS...)
}
if len(z.SIGNS) > 0 {
rrs = append(rrs, z.SIGNS...)
}
rrs := make([]dns.RR, 0, 1+len(a.SIGSOA)+len(a.NS)+len(a.SIGNS))
rrs = append(rrs, a.SOA)
rrs = append(rrs, a.SIGSOA...)
rrs = append(rrs, a.NS...)
rrs = append(rrs, a.SIGNS...)
return rrs, nil
}
// ApexIfDefined returns the apex nodes from z. The SOA record is the first record, if it does not exist, an error is returned.
func (z *Zone) ApexIfDefined() ([]dns.RR, error) {
ap, _ := z.snapshot()
return ap.records()
}
// NameFromRight returns the labels from the right, staring with the
// origin and then i labels extra. When we are overshooting the name
// the returned boolean is set to true.
@@ -197,12 +207,6 @@ func (z *Zone) nameFromRight(qname string, i int) (string, bool) {
return qname[n:], false
}
func (z *Zone) hasSOA() bool {
z.RLock()
defer z.RUnlock()
return z.SOA != nil
}
func (z *Zone) getSOA() *dns.SOA {
z.RLock()
defer z.RUnlock()