Files
coredns-netbox-plugin-dns/lookup.go
T
2024-05-07 19:51:30 -06:00

279 lines
5.6 KiB
Go

package netboxdns
import (
"strings"
"github.com/doubleu-labs/coredns-netbox-plugin-dns/internal/netbox"
"github.com/miekg/dns"
)
type lookupResult int
const (
lookupSuccess lookupResult = iota
lookupNameError // NXDomain
lookupDelegation // Delegate, non-authoritative
)
type lookupResponse struct {
Answer []dns.RR
Ns []dns.RR
Extra []dns.RR
LookupResult lookupResult
}
func (netboxdns *NetboxDNS) lookup(
name string,
qtype uint16,
family int,
) (*lookupResponse, error) {
nameTrimmed := strings.TrimSuffix(name, ".")
// check if zone exists on Netbox
zone, err := netboxdns.matchZone(nameTrimmed)
if err != nil {
return nil, err
}
if zone == nil {
return &lookupResponse{LookupResult: lookupNameError}, nil
}
// check if qname is for zone origin
if nameTrimmed == zone.Name {
originResponse, err := netboxdns.processOrigin(qtype, zone, family)
if err != nil {
return nil, err
}
if originResponse != nil {
return originResponse, nil
}
}
// lookup exact request
direct, err := netboxdns.lookupDirect(nameTrimmed, qtype, zone, family)
if err != nil {
return nil, err
}
if direct != nil {
return direct, nil
}
// if no exact records exist for the request, check if the qname is a
// delegate zone
delegate, err := netboxdns.lookupDelegate(nameTrimmed, zone, family)
if err != nil {
return nil, err
}
if delegate != nil {
return delegate, nil
}
return &lookupResponse{LookupResult: lookupNameError}, nil
}
func (netboxdns *NetboxDNS) matchZone(qname string) (*netbox.Zone, error) {
managedZones, err := netbox.GetZones(netboxdns.requestClient)
if err != nil {
return nil, err
}
var out *netbox.Zone
for _, managedZone := range managedZones {
if dns.IsSubDomain(managedZone.Name, qname) {
if out == nil {
out = &managedZone
}
if len(managedZone.Name) > len(out.Name) {
out = &managedZone
}
}
}
return out, nil
}
func (netboxdns *NetboxDNS) processOrigin(
qtype uint16,
zone *netbox.Zone,
family int,
) (*lookupResponse, error) {
var queryType []string
switch qtype {
case dns.TypeSOA:
queryType = []string{"SOA", "NS"}
case dns.TypeNS:
queryType = []string{"NS"}
default:
return nil, nil
}
records, err := netbox.GetRecordsQuery(
netboxdns.requestClient,
&netbox.RecordQuery{
Name: "@",
Type: queryType,
Zone: zone,
},
)
if err != nil {
return nil, err
}
rrs, err := recordsToRR(records)
if err != nil {
return nil, err
}
answer := filterRRByType(rrs, dns.TypeSOA)
ns := filterRRByType(rrs, dns.TypeNS)
extraRecords, err := netboxdns.processExtra(ns, zone, family)
if err != nil {
return nil, err
}
if len(extraRecords) == 0 {
// if no A/AAAA records exist for the NS in the specified zone, check if
// the server has records anywhere
extraRecords, err = netboxdns.processExtra(ns, nil, family)
if err != nil {
return nil, err
}
}
extra, err := recordsToRR(extraRecords)
if err != nil {
return nil, err
}
if qtype == dns.TypeNS {
answer = ns
ns = nil
}
return &lookupResponse{
Answer: answer,
Ns: ns,
Extra: extra,
}, nil
}
func (netboxdns *NetboxDNS) processExtra(
answer []dns.RR,
zone *netbox.Zone,
family int,
) ([]netbox.Record, error) {
var out []netbox.Record
for _, rr := range answer {
name := ""
switch t := rr.(type) {
case *dns.SRV:
name = t.Target
case *dns.MX:
name = t.Mx
case *dns.NS:
name = t.Ns
case *dns.CNAME:
name = t.Target
}
if len(name) == 0 {
continue
}
var reqType []string
switch family {
case 1:
reqType = []string{"A"}
case 2:
reqType = []string{"AAAA"}
}
records, err := netbox.GetRecordsQuery(
netboxdns.requestClient,
&netbox.RecordQuery{
FQDN: strings.TrimSuffix(name, "."),
Type: reqType,
Zone: zone,
},
)
if err != nil {
return out, err
}
out = append(out, records...)
}
return out, nil
}
func (netboxdns *NetboxDNS) lookupDirect(
qname string,
qtype uint16,
zone *netbox.Zone,
family int,
) (*lookupResponse, error) {
queryTypes := []string{dns.TypeToString[qtype]}
if qtype == dns.TypeA || qtype == dns.TypeAAAA {
queryTypes = append(queryTypes, "CNAME")
}
records, err := netbox.GetRecordsQuery(
netboxdns.requestClient,
&netbox.RecordQuery{
FQDN: qname,
Type: queryTypes,
Zone: zone,
},
)
if err != nil {
return nil, err
}
if len(records) > 0 {
answer, err := recordsToRR(records)
if err != nil {
return nil, err
}
extraRecords, err := netboxdns.processExtra(answer, zone, family)
if err != nil {
return nil, err
}
extra, err := recordsToRR(extraRecords)
if err != nil {
return nil, err
}
cnames := filterRRByType(answer, dns.TypeCNAME)
if qtype == dns.TypeCNAME || len(cnames) > 0 {
answer = append(answer, extra...)
extra = nil
}
return &lookupResponse{
Answer: answer,
Extra: extra,
}, nil
}
return nil, nil
}
func (netboxdns *NetboxDNS) lookupDelegate(
qname string,
zone *netbox.Zone,
family int,
) (*lookupResponse, error) {
records, err := netbox.GetRecordsQuery(
netboxdns.requestClient,
&netbox.RecordQuery{
FQDN: qname,
Type: []string{"NS"},
Zone: zone,
},
)
if err != nil {
return nil, err
}
if len(records) > 0 {
ns, err := recordsToRR(records)
if err != nil {
return nil, err
}
extraRecords, err := netboxdns.processExtra(ns, nil, family)
if err != nil {
return nil, err
}
extra, err := recordsToRR(extraRecords)
if err != nil {
return nil, err
}
return &lookupResponse{
Ns: ns,
Extra: extra,
LookupResult: lookupDelegation,
}, nil
}
return nil, nil
}