diff options
author | 2016-04-05 10:53:23 +0100 | |
---|---|---|
committer | 2016-04-05 10:53:23 +0100 | |
commit | c961acbb6e9e06279d3dca077ba47d8a6170da20 (patch) | |
tree | 8cbcb55515965a94d3fb0395a7d54e7e4e84c2a0 /middleware/file/secondary.go | |
parent | 20e16491ec7495adc07aef7d506463f15a2b6733 (diff) | |
download | coredns-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.go | 123 |
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) } |