After calling NewZone the number of records should be zero, but due to how zone.All() was implemented so empty RRs would be added. This then fails the == 0 check in xfr.go and put nil in the slice, this then subsequently panics on the Len(). Fix this making All() smarter when adding records. Added little test to enfore this. Signed-off-by: Miek Gieben <miek@miek.nl>
216 lines
4.9 KiB
Go
216 lines
4.9 KiB
Go
package file
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/coredns/coredns/plugin/file/tree"
|
|
"github.com/coredns/coredns/plugin/pkg/upstream"
|
|
"github.com/coredns/coredns/request"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// Zone defines a structure that contains all data related to a DNS zone.
|
|
type Zone struct {
|
|
origin string
|
|
origLen int
|
|
file string
|
|
*tree.Tree
|
|
Apex
|
|
apexMu sync.RWMutex
|
|
|
|
TransferTo []string
|
|
StartupOnce sync.Once
|
|
TransferFrom []string
|
|
Expired *bool
|
|
|
|
ReloadInterval time.Duration
|
|
reloadMu sync.RWMutex
|
|
reloadShutdown chan bool
|
|
Upstream *upstream.Upstream // Upstream for looking up external names during the resolution process.
|
|
}
|
|
|
|
// Apex contains the apex records of a zone: SOA, NS and their potential signatures.
|
|
type Apex struct {
|
|
SOA *dns.SOA
|
|
NS []dns.RR
|
|
SIGSOA []dns.RR
|
|
SIGNS []dns.RR
|
|
}
|
|
|
|
// NewZone returns a new zone.
|
|
func NewZone(name, file string) *Zone {
|
|
z := &Zone{
|
|
origin: dns.Fqdn(name),
|
|
origLen: dns.CountLabel(dns.Fqdn(name)),
|
|
file: filepath.Clean(file),
|
|
Tree: &tree.Tree{},
|
|
Expired: new(bool),
|
|
reloadShutdown: make(chan bool),
|
|
}
|
|
*z.Expired = false
|
|
return z
|
|
}
|
|
|
|
// Copy copies a zone.
|
|
func (z *Zone) Copy() *Zone {
|
|
z1 := NewZone(z.origin, z.file)
|
|
z1.TransferTo = z.TransferTo
|
|
z1.TransferFrom = z.TransferFrom
|
|
z1.Expired = z.Expired
|
|
|
|
z1.Apex = z.Apex
|
|
return z1
|
|
}
|
|
|
|
// CopyWithoutApex copies zone z without the Apex records.
|
|
func (z *Zone) CopyWithoutApex() *Zone {
|
|
z1 := NewZone(z.origin, z.file)
|
|
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) error {
|
|
r.Header().Name = strings.ToLower(r.Header().Name)
|
|
|
|
switch h := r.Header().Rrtype; h {
|
|
case dns.TypeNS:
|
|
r.(*dns.NS).Ns = strings.ToLower(r.(*dns.NS).Ns)
|
|
|
|
if r.Header().Name == z.origin {
|
|
z.Apex.NS = append(z.Apex.NS, r)
|
|
return nil
|
|
}
|
|
case dns.TypeSOA:
|
|
r.(*dns.SOA).Ns = strings.ToLower(r.(*dns.SOA).Ns)
|
|
r.(*dns.SOA).Mbox = strings.ToLower(r.(*dns.SOA).Mbox)
|
|
|
|
z.Apex.SOA = r.(*dns.SOA)
|
|
return nil
|
|
case dns.TypeNSEC3, dns.TypeNSEC3PARAM:
|
|
return fmt.Errorf("NSEC3 zone is not supported, dropping RR: %s for zone: %s", r.Header().Name, z.origin)
|
|
case dns.TypeRRSIG:
|
|
x := r.(*dns.RRSIG)
|
|
switch x.TypeCovered {
|
|
case dns.TypeSOA:
|
|
z.Apex.SIGSOA = append(z.Apex.SIGSOA, x)
|
|
return nil
|
|
case dns.TypeNS:
|
|
if r.Header().Name == z.origin {
|
|
z.Apex.SIGNS = append(z.Apex.SIGNS, x)
|
|
return nil
|
|
}
|
|
}
|
|
case dns.TypeCNAME:
|
|
r.(*dns.CNAME).Target = strings.ToLower(r.(*dns.CNAME).Target)
|
|
case dns.TypeMX:
|
|
r.(*dns.MX).Mx = strings.ToLower(r.(*dns.MX).Mx)
|
|
case dns.TypeSRV:
|
|
r.(*dns.SRV).Target = strings.ToLower(r.(*dns.SRV).Target)
|
|
}
|
|
|
|
z.Tree.Insert(r)
|
|
return nil
|
|
}
|
|
|
|
// Delete deletes r from z.
|
|
func (z *Zone) Delete(r dns.RR) { z.Tree.Delete(r) }
|
|
|
|
// File retrieves the file path in a safe way
|
|
func (z *Zone) File() string {
|
|
z.reloadMu.Lock()
|
|
defer z.reloadMu.Unlock()
|
|
return z.file
|
|
}
|
|
|
|
// SetFile updates the file path in a safe way
|
|
func (z *Zone) SetFile(path string) {
|
|
z.reloadMu.Lock()
|
|
z.file = path
|
|
z.reloadMu.Unlock()
|
|
}
|
|
|
|
// TransferAllowed checks if incoming request for transferring the zone is allowed according to the ACLs.
|
|
func (z *Zone) TransferAllowed(state request.Request) bool {
|
|
for _, t := range z.TransferTo {
|
|
if t == "*" {
|
|
return true
|
|
}
|
|
// If remote IP matches we accept.
|
|
remote := state.IP()
|
|
to, _, err := net.SplitHostPort(t)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if to == remote {
|
|
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 {
|
|
if z.ReloadInterval > 0 {
|
|
z.reloadMu.RLock()
|
|
defer z.reloadMu.RUnlock()
|
|
}
|
|
|
|
records := []dns.RR{}
|
|
allNodes := z.Tree.All()
|
|
for _, a := range allNodes {
|
|
records = append(records, a.All()...)
|
|
}
|
|
|
|
// Either the entire Apex is filled or none it, this isn't enforced here though.
|
|
if len(z.Apex.SIGNS) > 0 {
|
|
records = append(z.Apex.SIGNS, records...)
|
|
}
|
|
if len(z.Apex.NS) > 0 {
|
|
records = append(z.Apex.NS, records...)
|
|
}
|
|
|
|
if len(z.Apex.SIGSOA) > 0 {
|
|
records = append(z.Apex.SIGSOA, records...)
|
|
}
|
|
if z.Apex.SOA != nil {
|
|
return append([]dns.RR{z.Apex.SOA}, records...)
|
|
}
|
|
return records
|
|
}
|
|
|
|
// NameFromRight returns the labels from the right, staring with the
|
|
// origin and then i labels extra. When we are overshooting the name
|
|
// the returned boolean is set to true.
|
|
func (z *Zone) nameFromRight(qname string, i int) (string, bool) {
|
|
if i <= 0 {
|
|
return z.origin, false
|
|
}
|
|
|
|
for j := 1; j <= z.origLen; j++ {
|
|
if _, shot := dns.PrevLabel(qname, j); shot {
|
|
return qname, shot
|
|
}
|
|
}
|
|
|
|
k := 0
|
|
var shot bool
|
|
for j := 1; j <= i; j++ {
|
|
k, shot = dns.PrevLabel(qname, j+z.origLen)
|
|
if shot {
|
|
return qname, shot
|
|
}
|
|
}
|
|
return qname[k:], false
|
|
}
|