Ready for CoreDNS testing

This commit is contained in:
Dessa Simpson 2025-12-01 01:13:27 -07:00
parent 772469d21b
commit b79df4bf1d
9 changed files with 290 additions and 124 deletions

View file

@ -18,11 +18,24 @@ type Record struct {
Value string
}
type NamedRecord struct {
Name string
Record Record
}
type Zone struct {
Subzones map[string]*Zone
Records []Record
GlueRecords []Record
SOA NamedRecord
IsDelegationPoint bool
GlueRecords []NamedRecord // Only set (optionally) for delegation points
}
type LookupResult struct {
Answer []NamedRecord
Ns []NamedRecord
Extra []NamedRecord
IsReferral bool
}
type contextKey string
@ -57,31 +70,40 @@ func LoadZoneBytes(data []byte, filename string) (*Zone, error) {
if err != nil {
return nil, err
}
if err := z.Validate(".", false); err != nil {
if err := z.Validate(".", nil, nil); err != nil {
return nil, err
}
return z, nil
}
func (z *Zone) Validate(name string, zoneApexPresent bool) error {
nameservers := map[string]bool{}
otherRecords := []Record{}
func (z *Zone) Validate(name string, soa *Record, ns *[]Record) error {
nameservers, otherRecords := []Record{}, []Record{}
nameserversMap := map[string]bool{}
isZoneApex := false
cnameCount := 0
for _, record := range z.Records {
switch record.Type {
case "SOA":
isZoneApex = true
soa = &record
z.SOA = NamedRecord{Name: name, Record: record}
case "NS":
nameservers[record.Value] = true
nameservers = append(nameservers, record)
nameserversMap[record.Value] = true
case "CNAME":
cnameCount++
default:
otherRecords = append(otherRecords, record)
}
}
zoneApexPresent = zoneApexPresent || isZoneApex
if cnameCount > 1 || (cnameCount > 0 && len(otherRecords) > 0) {
return fmt.Errorf("%s: extraneous records found next to CNAME", name)
}
z.IsDelegationPoint = !isZoneApex && len(nameservers) > 0
if !zoneApexPresent {
if soa == nil {
// Outside zone
if z.IsDelegationPoint {
return fmt.Errorf("%s: delegation point found outside zone", name)
@ -93,51 +115,51 @@ func (z *Zone) Validate(name string, zoneApexPresent bool) error {
if len(nameservers) == 0 {
return fmt.Errorf("%s: zone apex missing NS records", name)
}
ns = &nameservers
} else if len(nameservers) > 0 {
// Delegation point (does not fall through to subzone validation)
if len(otherRecords) > 0 {
return fmt.Errorf("%s: non-glue, non-NS records found at delegation point: %v", name, otherRecords)
}
for subname, subzone := range z.Subzones {
glueRecords, err := subzone.GetGlueRecords(concatName(name, subname), nameservers)
// This populates z.GlueRecords directly
err := subzone.GetGlueRecords(concatName(name, subname), nameserversMap)
if err != nil {
return err
}
z.GlueRecords = append(z.GlueRecords, glueRecords...)
}
return nil
}
// Subzone validation
// Either we're outside a zone, at a zone apex, or at a non-delegated subzone
for subname, subzone := range z.Subzones {
if err := subzone.Validate(concatName(name, subname), zoneApexPresent); err != nil {
if err := subzone.Validate(concatName(name, subname), soa, ns); err != nil {
return err
}
}
return nil
}
func (z *Zone) GetGlueRecords(name string, nameservers map[string]bool) ([]Record, error) {
func (z *Zone) GetGlueRecords(name string, nameserversMap map[string]bool) error {
// If the domain is not a nameserver, it must have no records
if _, ok := nameservers[name]; !ok {
if _, ok := nameserversMap[strings.TrimSuffix(name, ".")]; !ok {
if len(z.Records) > 0 {
return nil, fmt.Errorf("%s: non-glue records found under delegation point: %v", name, z.Records)
return fmt.Errorf("%s: non-glue records found under delegation point: %v", name, z.Records)
}
}
// Any records under a delegation point must be glue records
for _, record := range z.Records {
if !(record.Type == "A" || record.Type == "AAAA") {
return nil, fmt.Errorf("%s: non-glue record found under delegation point: %v", name, record)
return fmt.Errorf("%s: non-glue record found under delegation point: %v", name, record)
}
z.GlueRecords = append(z.GlueRecords, NamedRecord{Name: name, Record: record})
}
for subname, subzone := range z.Subzones {
if glueRecords, err := subzone.GetGlueRecords(concatName(name, subname), nameservers); err != nil {
return nil, err
} else {
z.GlueRecords = append(z.GlueRecords, glueRecords...)
if err := subzone.GetGlueRecords(concatName(name, subname), nameserversMap); err != nil {
return err
}
}
return z.GlueRecords, nil
return nil
}
func (r *Record) UnmarshalYAML(ctx context.Context, data []byte) error {
@ -252,38 +274,67 @@ func nameToPath(name string) []string {
return path
}
func (z *Zone) Lookup(name string) ([]Record, bool) {
func (z *Zone) Lookup(name string) (LookupResult, bool) {
res := LookupResult{}
path := nameToPath(name)
for _, label := range path {
// Support empty name and trailing dot
if label == "" {
continue
}
if sz, ok := z.Subzones[label]; ok {
z = sz
if z.IsDelegationPoint {
res.IsReferral = true
// Capture NS records
for _, record := range z.Records {
if record.Type == "NS" {
res.Ns = append(res.Ns, NamedRecord{Name: name, Record: record})
}
}
// Retrieve glue records from cache
res.Extra = append(res.Extra, z.GlueRecords...)
return res, true
}
if sz, ok := z.Subzones[label]; !ok {
// NXDOMAIN
res.Ns = []NamedRecord{z.SOA}
return res, false
} else {
return nil, false
z = sz
}
}
return z.Records, true
res.Answer = []NamedRecord{}
for _, record := range z.Records {
res.Answer = append(res.Answer, NamedRecord{Name: name, Record: record})
}
if len(res.Answer) == 0 {
// NODATA
res.Ns = []NamedRecord{z.SOA}
return res, true
}
return res, true
}
func (z *Zone) FilterRecords(records []Record, recordType string) []Record {
filtered := []Record{}
for _, record := range records {
if record.Type == recordType {
filtered = append(filtered, record)
func (z *Zone) FilterRecords(res LookupResult, recordType string) LookupResult {
filtered := []NamedRecord{}
for _, nr := range res.Answer {
if nr.Record.Type == recordType {
filtered = append(filtered, nr)
}
}
return filtered
res.Answer = filtered
if len(res.Answer) == 0 && !res.IsReferral {
// This ends up as NODATA even if it had data before filtering
res.Ns = []NamedRecord{z.SOA}
}
return res
}
func (z *Zone) LookupType(name string, recordType string) ([]Record, bool) {
records, ok := z.Lookup(name)
func (z *Zone) LookupType(name string, recordType string) (LookupResult, bool) {
res, ok := z.Lookup(name)
if !ok {
return nil, false
return LookupResult{}, false
}
return z.FilterRecords(records, recordType), true
return z.FilterRecords(res, recordType), true
}
func concatName(name string, subname string) string {