Instead of watching a single file watch the entire directory and catch the .Name from the event. On any event, just reload the damn thing. This also fixes the problem of loosing events when the inode changes.
148 lines
3.2 KiB
Go
148 lines
3.2 KiB
Go
package file
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"sync"
|
|
|
|
"github.com/miekg/coredns/middleware"
|
|
"github.com/miekg/coredns/middleware/file/tree"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
type Zone struct {
|
|
SOA *dns.SOA
|
|
SIG []dns.RR
|
|
origin string
|
|
file string
|
|
*tree.Tree
|
|
|
|
TransferTo []string
|
|
StartupOnce sync.Once
|
|
TransferFrom []string
|
|
Expired *bool
|
|
|
|
NoReload bool
|
|
reloadMu sync.RWMutex
|
|
// TODO: shutdown watcher channel
|
|
}
|
|
|
|
// NewZone returns a new zone.
|
|
func NewZone(name, file string) *Zone {
|
|
z := &Zone{origin: dns.Fqdn(name), file: file, Tree: &tree.Tree{}, Expired: new(bool)}
|
|
*z.Expired = false
|
|
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.origin, z.file)
|
|
z1.TransferTo = z.TransferTo
|
|
z1.TransferFrom = z.TransferFrom
|
|
z1.Expired = z.Expired
|
|
z1.SOA = z.SOA
|
|
z1.SIG = z.SIG
|
|
return z1
|
|
}
|
|
|
|
// Insert inserts r into z.
|
|
func (z *Zone) Insert(r dns.RR) error {
|
|
switch h := r.Header().Rrtype; h {
|
|
case dns.TypeSOA:
|
|
z.SOA = r.(*dns.SOA)
|
|
return nil
|
|
case dns.TypeNSEC3, dns.TypeNSEC3PARAM:
|
|
return fmt.Errorf("NSEC3 zone is not supported, dropping")
|
|
case dns.TypeRRSIG:
|
|
if x, ok := r.(*dns.RRSIG); ok && x.TypeCovered == dns.TypeSOA {
|
|
z.SIG = append(z.SIG, x)
|
|
return nil
|
|
}
|
|
fallthrough
|
|
default:
|
|
z.Tree.Insert(r)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Delete deletes r from z.
|
|
func (z *Zone) Delete(r dns.RR) { z.Tree.Delete(r) }
|
|
|
|
// TransferAllowed checks if incoming request for transferring the zone is allowed according to the ACLs.
|
|
func (z *Zone) TransferAllowed(state middleware.State) bool {
|
|
for _, t := range z.TransferTo {
|
|
if t == "*" {
|
|
return true
|
|
}
|
|
}
|
|
// TODO(miek): future matching against IP/CIDR notations
|
|
return false
|
|
}
|
|
|
|
// All returns all records from the zone, the first record will be the SOA record,
|
|
// otionally followed by all RRSIG(SOA)s.
|
|
func (z *Zone) All() []dns.RR {
|
|
z.reloadMu.RLock()
|
|
defer z.reloadMu.RUnlock()
|
|
records := []dns.RR{}
|
|
allNodes := z.Tree.All()
|
|
for _, a := range allNodes {
|
|
records = append(records, a.All()...)
|
|
}
|
|
|
|
if len(z.SIG) > 0 {
|
|
records = append(z.SIG, records...)
|
|
}
|
|
return append([]dns.RR{z.SOA}, records...)
|
|
}
|
|
|
|
func (z *Zone) Reload(shutdown chan bool) error {
|
|
if z.NoReload {
|
|
return nil
|
|
}
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = watcher.Add(path.Dir(z.file))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
// TODO(miek): needs to be killed on reload.
|
|
for {
|
|
select {
|
|
case event := <-watcher.Events:
|
|
if event.Name == z.file {
|
|
reader, err := os.Open(z.file)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to open `%s' for `%s': %v", z.file, z.origin, err)
|
|
continue
|
|
}
|
|
z.reloadMu.Lock()
|
|
zone, err := Parse(reader, z.origin, z.file)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to parse `%s': %v", z.origin, err)
|
|
z.reloadMu.Unlock()
|
|
continue
|
|
}
|
|
// copy elements we need
|
|
z.SOA = zone.SOA
|
|
z.SIG = zone.SIG
|
|
z.Tree = zone.Tree
|
|
z.reloadMu.Unlock()
|
|
log.Printf("[INFO] Successfully reloaded zone `%s'", z.origin)
|
|
}
|
|
case <-shutdown:
|
|
watcher.Close()
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
return nil
|
|
}
|