feat(secondary): Send NOTIFY messages after zone transfer (#7901)

* feat(secondary): Send NOTIFY messages after zone transfer

- Modified TransferIn() method to accept a transfer.Transfer parameter
- Added NOTIFY message sending after successful zone transfer in secondary plugin
- Updated Update() method to pass the transfer handler through the zone update cycle
- Added comprehensive tests for the secondary notify functionality

Closes #5669

Signed-off-by: liucongran <liucongran327@gmail.com>

* fix(secondary): Fix TransferIn method call in test

Update test to pass nil parameter to TransferIn method after signature change

Signed-off-by: liucongran <liucongran327@gmail.com>

* refactor(secondary): Clean up imports and add helper methods

- Reorder imports for consistency
- Add hasSOA() and getSOA() helper methods to Zone
- Remove unnecessary blank lines in tests

Signed-off-by: liucongran <liucongran327@gmail.com>

* fix(test): Fix variable declaration in secondary test

Change corefile variable assignment to use short declaration syntax (:=)
to fix compilation error.

Signed-off-by: liucongran <liucongran327@gmail.com>

* refactor(secondary): Use getSOA helper method in shouldTransfer

Replace direct SOA access with getSOA() helper method for consistency.

Signed-off-by: liucongran <liucongran327@gmail.com>

---------

Signed-off-by: liucongran <liucongran327@gmail.com>
Co-authored-by: liucongran <liucongran@cestc.cn>
This commit is contained in:
liucongran
2026-03-08 13:15:44 +08:00
committed by GitHub
parent 90a9739478
commit 2daf48e42d
7 changed files with 170 additions and 20 deletions

View File

@@ -22,7 +22,7 @@ type (
File struct {
Next plugin.Handler
Zones
transfer *transfer.Transfer
Xfer *transfer.Transfer
Fall fall.F
}
@@ -70,7 +70,7 @@ func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
log.Infof("Notify from %s for %s: checking transfer", state.IP(), zone)
ok, err := z.shouldTransfer()
if ok {
z.TransferIn()
z.TransferIn(f.Xfer)
} else {
log.Infof("Notify from %s for %s: no SOA serial increase seen", state.IP(), zone)
}

View File

@@ -4,11 +4,13 @@ import (
"math/rand"
"time"
"github.com/coredns/coredns/plugin/transfer"
"github.com/miekg/dns"
)
// TransferIn retrieves the zone from the masters, parses it and sets it live.
func (z *Zone) TransferIn() error {
func (z *Zone) TransferIn(t *transfer.Transfer) error {
if len(z.TransferFrom) == 0 {
return nil
}
@@ -57,6 +59,13 @@ Transfer:
z.Expired = false
z.Unlock()
log.Infof("Transferred: %s from %s", z.origin, tr)
// Send notify messages to secondary servers
if t != nil {
if err := t.Notify(z.origin); err != nil {
log.Warningf("Failed sending notifies: %s", err)
}
}
return nil
}
@@ -89,10 +98,11 @@ Transfer:
if serial == -1 {
return false, Err
}
if z.SOA == nil {
if !z.hasSOA() {
return true, Err
}
return less(z.SOA.Serial, uint32(serial)), Err // #nosec G115 -- serial fits in uint32 per DNS RFC
soa := z.getSOA()
return less(soa.Serial, uint32(serial)), Err // #nosec G115 -- serial fits in uint32 per DNS RFC
}
// less returns true of a is smaller than b when taking RFC 1982 serial arithmetic into account.
@@ -107,17 +117,18 @@ func less(a, b uint32) bool {
// and uses the SOA parameters. Every refresh it will check for a new SOA number. If that fails (for all
// server) it will retry every retry interval. If the zone failed to transfer before the expire, the zone
// will be marked expired.
func (z *Zone) Update(updateShutdown chan bool) error {
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.SOA == nil {
for !z.hasSOA() {
time.Sleep(1 * time.Second)
}
retryActive := false
Restart:
refresh := time.Second * time.Duration(z.SOA.Refresh)
retry := time.Second * time.Duration(z.SOA.Retry)
expire := time.Second * time.Duration(z.SOA.Expire)
soa := z.getSOA()
refresh := time.Second * time.Duration(soa.Refresh)
retry := time.Second * time.Duration(soa.Retry)
expire := time.Second * time.Duration(soa.Expire)
refreshTicker := time.NewTicker(refresh)
retryTicker := time.NewTicker(retry)
@@ -145,7 +156,7 @@ Restart:
}
if ok {
if err := z.TransferIn(); err != nil {
if err := z.TransferIn(t); err != nil {
// transfer failed, leave retryActive true
break
}
@@ -170,7 +181,7 @@ Restart:
}
if ok {
if err := z.TransferIn(); err != nil {
if err := z.TransferIn(t); err != nil {
// transfer failed
retryActive = true
break

View File

@@ -113,7 +113,7 @@ func TestTransferIn(t *testing.T) {
z.origin = testZone
z.TransferFrom = []string{s.Addr}
if err := z.TransferIn(); err != nil {
if err := z.TransferIn(nil); err != nil {
t.Fatalf("Unable to run TransferIn: %v", err)
}
if z.SOA.String() != fmt.Sprintf("%s 3600 IN SOA bla. bla. 250 0 0 0 0", testZone) {

View File

@@ -29,10 +29,10 @@ func setup(c *caddy.Controller) error {
if t == nil {
return nil
}
f.transfer = t.(*transfer.Transfer) // if found this must be OK.
f.Xfer = t.(*transfer.Transfer) // if found this must be OK.
go func() {
for _, n := range zones.Names {
f.transfer.Notify(n)
f.Xfer.Notify(n)
}
}()
return nil
@@ -45,7 +45,7 @@ func setup(c *caddy.Controller) error {
}
go func() {
for _, n := range zones.Names {
f.transfer.Notify(n)
f.Xfer.Notify(n)
}
}()
return nil
@@ -55,7 +55,7 @@ func setup(c *caddy.Controller) error {
z := zones.Z[n]
c.OnShutdown(z.OnShutdown)
c.OnStartup(func() error {
z.StartupOnce.Do(func() { z.Reload(f.transfer) })
z.StartupOnce.Do(func() { z.Reload(f.Xfer) })
return nil
})
}

View File

@@ -182,3 +182,15 @@ 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()
return z.SOA
}