aboutsummaryrefslogtreecommitdiff
path: root/middleware/file/secondary.go
diff options
context:
space:
mode:
authorGravatar Miek Gieben <miek@miek.nl> 2016-04-05 10:53:23 +0100
committerGravatar Miek Gieben <miek@miek.nl> 2016-04-05 10:53:23 +0100
commitc961acbb6e9e06279d3dca077ba47d8a6170da20 (patch)
tree8cbcb55515965a94d3fb0395a7d54e7e4e84c2a0 /middleware/file/secondary.go
parent20e16491ec7495adc07aef7d506463f15a2b6733 (diff)
downloadcoredns-c961acbb6e9e06279d3dca077ba47d8a6170da20.tar.gz
coredns-c961acbb6e9e06279d3dca077ba47d8a6170da20.tar.zst
coredns-c961acbb6e9e06279d3dca077ba47d8a6170da20.zip
Add complete secondary support
Respond to notifies and allow a secondary to follow the SOA parameters to update a zone from a primary. Also sprinkle it with logging. Also extend monitoring to include qtype in more metrics.
Diffstat (limited to 'middleware/file/secondary.go')
-rw-r--r--middleware/file/secondary.go123
1 files changed, 117 insertions, 6 deletions
diff --git a/middleware/file/secondary.go b/middleware/file/secondary.go
index 53e66cebe..4c39e805c 100644
--- a/middleware/file/secondary.go
+++ b/middleware/file/secondary.go
@@ -2,6 +2,9 @@ package file
import (
"log"
+ "time"
+
+ "github.com/miekg/coredns/middleware"
"github.com/miekg/dns"
)
@@ -11,16 +14,18 @@ func (z *Zone) TransferIn() error {
if len(z.TransferFrom) == 0 {
return nil
}
- t := new(dns.Transfer)
m := new(dns.Msg)
m.SetAxfr(z.name)
+ z1 := z.Copy()
var Err error
+
Transfer:
for _, tr := range z.TransferFrom {
+ t := new(dns.Transfer)
c, err := t.In(m, tr)
if err != nil {
- log.Printf("[ERROR] Failed to setup transfer %s with %s: %v", z.name, z.TransferFrom[0], err)
+ log.Printf("[ERROR] Failed to setup transfer %s with %s: %v", z.name, tr, err)
Err = err
continue Transfer
}
@@ -32,21 +37,127 @@ Transfer:
}
for _, rr := range env.RR {
if rr.Header().Rrtype == dns.TypeSOA {
- z.SOA = rr.(*dns.SOA)
+ z1.SOA = rr.(*dns.SOA)
continue
}
if rr.Header().Rrtype == dns.TypeRRSIG {
if x, ok := rr.(*dns.RRSIG); ok && x.TypeCovered == dns.TypeSOA {
- z.SIG = append(z.SIG, x)
+ z1.SIG = append(z1.SIG, x)
}
}
- z.Insert(rr)
+ z1.Insert(rr)
}
}
+ Err = nil
+ break
}
if Err != nil {
log.Printf("[ERROR] Failed to transfer %s", z.name)
+ return nil
+ }
+
+ z.Tree = z1.Tree
+ *z.Expired = false
+ log.Printf("[INFO] Transfered: %s", z.name)
+ return nil
+}
+
+// shouldTransfer checks the primaries of zone, retrieves the SOA record, checks the current serial
+// and the remote serial and will return true if the remote one is higher than the locally configured one.
+func (z *Zone) shouldTransfer() (bool, error) {
+ c := new(dns.Client)
+ c.Net = "tcp" // do this query over TCP to minimize spoofing
+ m := new(dns.Msg)
+ m.SetQuestion(z.name, dns.TypeSOA)
+
+ var Err error
+ serial := -1
+
+ for _, tr := range z.TransferFrom {
+ Err = nil
+ ret, err := middleware.Exchange(c, m, tr)
+ if err != nil || ret.Rcode != dns.RcodeSuccess {
+ Err = err
+ continue
+ }
+ for _, a := range ret.Answer {
+ if a.Header().Rrtype == dns.TypeSOA {
+ serial = int(a.(*dns.SOA).Serial)
+ }
+ }
+ }
+ if serial == -1 {
+ return false, Err
+ }
+ return less(z.SOA.Serial, uint32(serial)), Err
+}
+
+// less return true of a is smaller than b when taking RFC 1982 serial arithmetic into account.
+func less(a, b uint32) bool {
+ // TODO(miek): implement!
+ return a < b
+}
+
+// Update updates the secondary zone according to its SOA. It will run for the life time of the server
+// and uses the SOA parameters. Every refresh it will check for a new SOA number. If that fails (for all
+// server) it wil retry every retry interval. If the zone failed to transfer before the expire, the zone
+// will be marked expired.
+func (z *Zone) Update() error {
+ // TODO(miek): if SOA changes we need to redo this with possible different timer values.
+ // TODO(miek): yeah...
+ for z.SOA == nil {
+ time.Sleep(1 * time.Second)
+ }
+
+ refresh := time.Second * time.Duration(z.SOA.Refresh)
+ retry := time.Second * time.Duration(z.SOA.Retry)
+ expire := time.Second * time.Duration(z.SOA.Expire)
+ retryActive := false
+
+ // TODO(miek): check max as well?
+ if refresh < time.Hour {
+ refresh = time.Hour
+ }
+ if retry < time.Hour {
+ retry = time.Hour
}
+
+ refreshTicker := time.NewTicker(refresh)
+ retryTicker := time.NewTicker(retry)
+ expireTicker := time.NewTicker(expire)
+
+ for {
+ select {
+ case <-expireTicker.C:
+ if !retryActive {
+ break
+ }
+ // TODO(miek): should actually keep track of last succesfull transfer
+ *z.Expired = true
+
+ case <-retryTicker.C:
+ if !retryActive {
+ break
+ }
+ ok, err := z.shouldTransfer()
+ if err != nil && ok {
+ log.Printf("[INFO] Refreshing zone: %s: initiating transfer", z.name)
+ z.TransferIn()
+ retryActive = false
+ }
+
+ case <-refreshTicker.C:
+ ok, err := z.shouldTransfer()
+ retryActive = err != nil
+ if err != nil && ok {
+ log.Printf("[INFO] Refreshing zone: %s: initiating transfer", z.name)
+ z.TransferIn()
+ }
+ }
+ }
+
+ refreshTicker.Stop()
+ retryTicker.Stop()
+ expireTicker.Stop()
return nil
- return Err // ignore errors for now. TODO(miek)
}