aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/setup/file.go14
-rw-r--r--core/setup/secondary.go16
-rw-r--r--middleware/file/file.go22
-rw-r--r--middleware/file/notify.go30
-rw-r--r--middleware/file/secondary.go123
-rw-r--r--middleware/file/zone.go9
-rw-r--r--middleware/prometheus/handler.go6
-rw-r--r--middleware/prometheus/metrics.go8
8 files changed, 207 insertions, 21 deletions
diff --git a/core/setup/file.go b/core/setup/file.go
index f501cf518..edcddd30c 100644
--- a/core/setup/file.go
+++ b/core/setup/file.go
@@ -14,6 +14,16 @@ func File(c *Controller) (middleware.Middleware, error) {
return nil, err
}
+ // Add startup functions to notify the master.
+ for _, n := range zones.Names {
+ if len(zones.Z[n].TransferTo) > 0 {
+ c.Startup = append(c.Startup, func() error {
+ zones.Z[n].Notify()
+ return err
+ })
+ }
+ }
+
return func(next middleware.Handler) middleware.Handler {
return file.File{Next: next, Zones: zones}
}, nil
@@ -58,7 +68,9 @@ func fileParse(c *Controller) (file.Zones, error) {
}
// discard from, here, maybe check and show log when we do?
for _, origin := range origins {
- z[origin].TransferTo = append(z[origin].TransferTo, t)
+ if t != "" {
+ z[origin].TransferTo = append(z[origin].TransferTo, t)
+ }
}
}
}
diff --git a/core/setup/secondary.go b/core/setup/secondary.go
index c7b5cc8ce..2f620c43e 100644
--- a/core/setup/secondary.go
+++ b/core/setup/secondary.go
@@ -13,13 +13,19 @@ func Secondary(c *Controller) (middleware.Middleware, error) {
return nil, err
}
- // Setup retrieve the zone.
+ // Add startup functions to retrieve the zone and keep it up to date.
for _, n := range zones.Names {
if len(zones.Z[n].TransferFrom) > 0 {
c.Startup = append(c.Startup, func() error {
err := zones.Z[n].TransferIn()
return err
})
+ c.Startup = append(c.Startup, func() error {
+ go func() {
+ zones.Z[n].Update()
+ }()
+ return nil
+ })
}
}
@@ -52,8 +58,12 @@ func secondaryParse(c *Controller) (file.Zones, error) {
return file.Zones{}, e
}
for _, origin := range origins {
- z[origin].TransferTo = append(z[origin].TransferTo, t)
- z[origin].TransferFrom = append(z[origin].TransferFrom, f)
+ if t != "" {
+ z[origin].TransferTo = append(z[origin].TransferTo, t)
+ }
+ if f != "" {
+ z[origin].TransferFrom = append(z[origin].TransferFrom, f)
+ }
}
}
}
diff --git a/middleware/file/file.go b/middleware/file/file.go
index a647f9d12..473b55574 100644
--- a/middleware/file/file.go
+++ b/middleware/file/file.go
@@ -28,7 +28,6 @@ func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
if state.QClass() != dns.ClassINET {
return dns.RcodeServerFailure, fmt.Errorf("file: can only deal with ClassINET")
}
-
qname := state.Name()
zone := middleware.Zones(f.Zones.Names).Matches(qname)
if zone == "" {
@@ -41,7 +40,26 @@ func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (i
if z == nil {
return dns.RcodeServerFailure, nil
}
+ if r.Opcode == dns.OpcodeNotify {
+ if z.isNotify(state) {
+ m := new(dns.Msg)
+ m.SetReply(r)
+ m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
+ w.WriteMsg(m)
+
+ if ok, _ := z.shouldTransfer(); ok {
+ log.Printf("[INFO] Valid notify from %s for %s: initiating transfer", state.IP(), zone)
+ z.TransferIn()
+ }
+
+ return dns.RcodeSuccess, nil
+ }
+ log.Printf("[INFO] Dropping notify from %s for %s", state.IP(), zone)
+ return dns.RcodeSuccess, nil
+ }
+
if z.Expired != nil && *z.Expired {
+ log.Printf("[ERROR] Zone %s is expired", zone)
return dns.RcodeServerFailure, nil
}
@@ -81,7 +99,7 @@ func Parse(f io.Reader, origin, fileName string) (*Zone, error) {
z := NewZone(origin)
for x := range tokens {
if x.Error != nil {
- log.Printf("[ERROR] failed to parse %s: %v", origin, x.Error)
+ log.Printf("[ERROR] Failed to parse %s: %v", origin, x.Error)
return nil, x.Error
}
if x.RR.Header().Rrtype == dns.TypeSOA {
diff --git a/middleware/file/notify.go b/middleware/file/notify.go
index 6667fb05d..a88ca9192 100644
--- a/middleware/file/notify.go
+++ b/middleware/file/notify.go
@@ -9,7 +9,26 @@ import (
"github.com/miekg/dns"
)
-// Notify will send notifies to all configured IP addresses.
+// isNotify checks if state is a notify message and if so, will *also* check if it
+// is from one of the configured masters. If not it will not be a valid notify
+// message. If the zone z is not a secondary zone the message will also be ignored.
+func (z *Zone) isNotify(state middleware.State) bool {
+ if state.Req.Opcode != dns.OpcodeNotify {
+ return false
+ }
+ if len(z.TransferFrom) == 0 {
+ return false
+ }
+ remote := middleware.Addr(state.IP()).Normalize()
+ for _, from := range z.TransferFrom {
+ if from == remote {
+ return true
+ }
+ }
+ return false
+}
+
+// Notify will send notifies to all configured TransferTo IP addresses.
func (z *Zone) Notify() {
go notify(z.name, z.TransferTo)
}
@@ -23,6 +42,10 @@ func notify(zone string, to []string) error {
c := new(dns.Client)
for _, t := range to {
+ // TODO(miek): these ACLs thingies not to be formalized.
+ if t == "*" {
+ continue
+ }
if err := notifyAddr(c, m, t); err != nil {
log.Printf("[ERROR] " + err.Error())
} else {
@@ -35,7 +58,10 @@ func notify(zone string, to []string) error {
func notifyAddr(c *dns.Client, m *dns.Msg, s string) error {
for i := 0; i < 3; i++ {
ret, err := middleware.Exchange(c, m, s)
- if err == nil && ret.Rcode == dns.RcodeSuccess || ret.Rcode == dns.RcodeNotImplemented {
+ if err != nil {
+ continue
+ }
+ if ret.Rcode == dns.RcodeSuccess || ret.Rcode == dns.RcodeNotImplemented {
return nil
}
}
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)
}
diff --git a/middleware/file/zone.go b/middleware/file/zone.go
index 3d291ba33..57d0cd4a0 100644
--- a/middleware/file/zone.go
+++ b/middleware/file/zone.go
@@ -25,6 +25,15 @@ func NewZone(name string) *Zone {
return z
}
+// Copy copies a zone *without* copying the zone's content. It is not a deep copy.
+func (z *Zone) Copy() *Zone {
+ z1 := NewZone(z.name)
+ z1.TransferTo = z.TransferTo
+ z1.TransferFrom = z.TransferFrom
+ z1.Expired = z.Expired
+ return z1
+}
+
// Insert inserts r into z.
func (z *Zone) Insert(r dns.RR) { z.Tree.Insert(r) }
diff --git a/middleware/prometheus/handler.go b/middleware/prometheus/handler.go
index a0cfcc872..3c07cd942 100644
--- a/middleware/prometheus/handler.go
+++ b/middleware/prometheus/handler.go
@@ -25,9 +25,9 @@ func (m *Metrics) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
status, err := m.Next.ServeDNS(ctx, rw, r)
requestCount.WithLabelValues(zone, qtype).Inc()
- requestDuration.WithLabelValues(zone).Observe(float64(time.Since(rw.Start()) / time.Second))
- responseSize.WithLabelValues(zone).Observe(float64(rw.Size()))
- responseRcode.WithLabelValues(zone, strconv.Itoa(rw.Rcode())).Inc()
+ requestDuration.WithLabelValues(zone, qtype).Observe(float64(time.Since(rw.Start()) / time.Second))
+ responseSize.WithLabelValues(zone, qtype).Observe(float64(rw.Size()))
+ responseRcode.WithLabelValues(zone, strconv.Itoa(rw.Rcode()), qtype).Inc()
return status, err
}
diff --git a/middleware/prometheus/metrics.go b/middleware/prometheus/metrics.go
index d6c4249e0..f030a6fed 100644
--- a/middleware/prometheus/metrics.go
+++ b/middleware/prometheus/metrics.go
@@ -53,7 +53,7 @@ func define(subsystem string) {
Namespace: namespace,
Subsystem: subsystem,
Name: "request_count_total",
- Help: "Counter of DNS requests made per zone and type.",
+ Help: "Counter of DNS requests made per zone and type and opcode.",
}, []string{"zone", "qtype"})
requestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
@@ -61,7 +61,7 @@ func define(subsystem string) {
Subsystem: subsystem,
Name: "request_duration_seconds",
Help: "Histogram of the time (in seconds) each request took.",
- }, []string{"zone"})
+ }, []string{"zone", "qtype"})
responseSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
@@ -69,12 +69,12 @@ func define(subsystem string) {
Name: "response_size_bytes",
Help: "Size of the returns response in bytes.",
Buckets: []float64{0, 100, 200, 300, 400, 511, 1023, 2047, 4095, 8291, 16e3, 32e3, 48e3, 64e3},
- }, []string{"zone"})
+ }, []string{"zone", "qtype"})
responseRcode = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "response_rcode_count_total",
Help: "Counter of response status codes.",
- }, []string{"zone", "rcode"})
+ }, []string{"zone", "rcode", "qtype"})
}