This commit is contained in:
@@ -15,7 +15,7 @@ type APIRequestClient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type APIResultModel interface {
|
type APIResultModel interface {
|
||||||
Record | Zone
|
Record | Zone | View
|
||||||
}
|
}
|
||||||
|
|
||||||
type APIManyResponse[T APIResultModel] struct {
|
type APIManyResponse[T APIResultModel] struct {
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package netbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Prefix struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Prefix string `json:"prefix"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type View struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Prefixes []Prefix `json:"prefixes"`
|
||||||
|
Default bool `json:"default_view`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v View) ContainsIP(IP netip.Addr) (bool, error) {
|
||||||
|
for _, prefixObj := range v.Prefixes {
|
||||||
|
prefix, err := netip.ParsePrefix(prefixObj.Prefix)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix.Contains(IP) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlViewID(netboxurl *url.URL, id int) *url.URL {
|
||||||
|
return netboxurl.JoinPath("views", "/", strconv.Itoa(id), "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetView(requestClient *APIRequestClient, id int) (View, error) {
|
||||||
|
requestUrl := urlViewID(requestClient.NetboxURL, id)
|
||||||
|
view, err := get[View](requestClient, requestUrl.String())
|
||||||
|
if err != nil {
|
||||||
|
return View{}, err
|
||||||
|
}
|
||||||
|
return view, nil
|
||||||
|
}
|
||||||
@@ -10,6 +10,10 @@ type Zone struct {
|
|||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
NameServers []SOAMName `json:"nameservers"`
|
NameServers []SOAMName `json:"nameservers"`
|
||||||
|
View struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SOAMName struct {
|
type SOAMName struct {
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package netboxdns
|
package netboxdns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/log"
|
||||||
"github.com/doubleu-labs/coredns-netbox-plugin-dns/internal/netbox"
|
"github.com/doubleu-labs/coredns-netbox-plugin-dns/internal/netbox"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -24,88 +27,138 @@ type lookupResponse struct {
|
|||||||
|
|
||||||
func (netboxdns *NetboxDNS) lookup(
|
func (netboxdns *NetboxDNS) lookup(
|
||||||
name string,
|
name string,
|
||||||
|
reqIP netip.Addr,
|
||||||
qtype uint16,
|
qtype uint16,
|
||||||
family int,
|
family int,
|
||||||
) (*lookupResponse, error) {
|
) (*lookupResponse, error) {
|
||||||
|
logger.Debugf("request for [%v] %v from %v\n", dns.TypeToString[qtype], name, reqIP)
|
||||||
|
|
||||||
nameTrimmed := strings.TrimSuffix(name, ".")
|
nameTrimmed := strings.TrimSuffix(name, ".")
|
||||||
// check if zone exists on Netbox
|
// check if zone exists on Netbox
|
||||||
zone, err := netboxdns.matchZone(nameTrimmed)
|
zones, default_zone_index, err := netboxdns.matchZone(nameTrimmed, reqIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if zone == nil {
|
if zones == nil {
|
||||||
logger.Debugf("no zone matching %q", name)
|
logger.Debugf("no zone matching %q", name)
|
||||||
return &lookupResponse{LookupResult: lookupNameError}, nil
|
return &lookupResponse{LookupResult: lookupNameError}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// var responses []*lookupResponse
|
||||||
|
var defaultResponse *lookupResponse
|
||||||
|
for i, zone := range zones {
|
||||||
|
is_zone_default := i == default_zone_index
|
||||||
// check if qname is for zone origin
|
// check if qname is for zone origin
|
||||||
if nameTrimmed == zone.Name {
|
if nameTrimmed == zone.Name {
|
||||||
originResponse, err := netboxdns.processOrigin(qtype, zone, family)
|
originResponse, err := netboxdns.processOrigin(qtype, zone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.Debugf("Could not process origin for zone %v: %w", zone, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if originResponse != nil {
|
if originResponse != nil {
|
||||||
logger.Debugf(
|
logger.Debugf(
|
||||||
"found origin records for [%s] %q",
|
"found origin records for [%s] %q in zone %v",
|
||||||
dns.TypeToString[qtype],
|
dns.TypeToString[qtype],
|
||||||
name,
|
name,
|
||||||
|
zone.Name,
|
||||||
)
|
)
|
||||||
|
if is_zone_default {
|
||||||
return originResponse, nil
|
return originResponse, nil
|
||||||
|
} else {
|
||||||
|
defaultResponse = originResponse
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookup exact request
|
// lookup exact request
|
||||||
direct, err := netboxdns.lookupDirect(nameTrimmed, qtype, zone, family)
|
direct, err := netboxdns.lookupDirect(nameTrimmed, qtype, zone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.Debugf("could not lookup exact request for %v in zone %v: %w", nameTrimmed, zone.Name, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if direct != nil {
|
if direct != nil {
|
||||||
logger.Debugf(
|
logger.Debugf(
|
||||||
"found records for [%s] %q",
|
"found records for [%s] %q in zone %v",
|
||||||
dns.TypeToString[qtype],
|
dns.TypeToString[qtype],
|
||||||
name,
|
name,
|
||||||
|
zone.View.Name,
|
||||||
)
|
)
|
||||||
|
if is_zone_default {
|
||||||
return direct, nil
|
return direct, nil
|
||||||
|
} else {
|
||||||
|
defaultResponse = direct
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no exact records exist for the request, check if the qname is a
|
// if no exact records exist for the request, check if the qname is a
|
||||||
// delegate zone
|
// delegate zone
|
||||||
delegate, err := netboxdns.lookupDelegate(nameTrimmed, zone, family)
|
delegate, err := netboxdns.lookupDelegate(nameTrimmed, zone, qtype)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.Debugf("could not lookup delegate for %v in zone %v: %w", nameTrimmed, zone.Name, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if delegate != nil {
|
if delegate != nil {
|
||||||
logger.Debugf("found delegate zone records for %q", name)
|
logger.Debugf("found delegate zone records for %q in zone %v", name, zone.Name)
|
||||||
|
// return delegate, nil
|
||||||
|
if is_zone_default {
|
||||||
return delegate, nil
|
return delegate, nil
|
||||||
|
} else {
|
||||||
|
defaultResponse = delegate
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("no records found for [%s] %q", dns.TypeToString[qtype], name)
|
continue
|
||||||
return &lookupResponse{LookupResult: lookupNameError}, nil
|
}
|
||||||
|
}
|
||||||
|
if defaultResponse != nil {
|
||||||
|
return defaultResponse, nil
|
||||||
|
} else {
|
||||||
|
log.Errorf("could not resolve any records for request %v", nameTrimmed)
|
||||||
|
return nil, fmt.Errorf("could not resolve any records for request %v ", nameTrimmed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (netboxdns *NetboxDNS) matchZone(qname string) (*netbox.Zone, error) {
|
func (netboxdns *NetboxDNS) matchZone(qname string, reqIP netip.Addr) ([]*netbox.Zone, int, error) {
|
||||||
managedZones, err := netbox.GetZones(netboxdns.requestClient)
|
managedZones, err := netbox.GetZones(netboxdns.requestClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
var out *netbox.Zone
|
var out []*netbox.Zone
|
||||||
|
index_of_default := -1
|
||||||
for _, managedZone := range managedZones {
|
for _, managedZone := range managedZones {
|
||||||
|
view, err := netbox.GetView(netboxdns.requestClient, managedZone.View.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
viewContainsIP, err := view.ContainsIP(reqIP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if !viewContainsIP {
|
||||||
|
log.Debugf("view %v's configured prefixes don't match request IP %v", view.Name, reqIP.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Debugf("view %v's configured prefixes match request IP %v", view.Name, reqIP.String())
|
||||||
|
|
||||||
if dns.IsSubDomain(managedZone.Name, qname) {
|
if dns.IsSubDomain(managedZone.Name, qname) {
|
||||||
if out == nil {
|
out = append(out, &managedZone)
|
||||||
out = &managedZone
|
if view.Default {
|
||||||
|
if index_of_default != -1 {
|
||||||
|
log.Errorf("more than one default view configured for IP %v", reqIP.String())
|
||||||
|
return nil, 0, fmt.Errorf("more than one default view configured for IP %v", reqIP.String())
|
||||||
}
|
}
|
||||||
if len(managedZone.Name) > len(out.Name) {
|
index_of_default = len(out)
|
||||||
out = &managedZone
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, index_of_default, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (netboxdns *NetboxDNS) processOrigin(
|
func (netboxdns *NetboxDNS) processOrigin(
|
||||||
qtype uint16,
|
qtype uint16,
|
||||||
zone *netbox.Zone,
|
zone *netbox.Zone,
|
||||||
family int,
|
|
||||||
) (*lookupResponse, error) {
|
) (*lookupResponse, error) {
|
||||||
var queryType []string
|
var queryType []string
|
||||||
switch qtype {
|
switch qtype {
|
||||||
@@ -133,14 +186,14 @@ func (netboxdns *NetboxDNS) processOrigin(
|
|||||||
}
|
}
|
||||||
answer := filterRRByType(rrs, dns.TypeSOA)
|
answer := filterRRByType(rrs, dns.TypeSOA)
|
||||||
ns := filterRRByType(rrs, dns.TypeNS)
|
ns := filterRRByType(rrs, dns.TypeNS)
|
||||||
extraRecords, err := netboxdns.processExtra(ns, zone, family)
|
extraRecords, err := netboxdns.processExtra(ns, zone, qtype)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(extraRecords) == 0 {
|
if len(extraRecords) == 0 {
|
||||||
// if no A/AAAA records exist for the NS in the specified zone, check if
|
// if no A/AAAA records exist for the NS in the specified zone, check if
|
||||||
// the server has records anywhere
|
// the server has records anywhere
|
||||||
extraRecords, err = netboxdns.processExtra(ns, nil, family)
|
extraRecords, err = netboxdns.processExtra(ns, nil, qtype)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -163,7 +216,7 @@ func (netboxdns *NetboxDNS) processOrigin(
|
|||||||
func (netboxdns *NetboxDNS) processExtra(
|
func (netboxdns *NetboxDNS) processExtra(
|
||||||
answer []dns.RR,
|
answer []dns.RR,
|
||||||
zone *netbox.Zone,
|
zone *netbox.Zone,
|
||||||
family int,
|
qtype uint16,
|
||||||
) ([]netbox.Record, error) {
|
) ([]netbox.Record, error) {
|
||||||
var out []netbox.Record
|
var out []netbox.Record
|
||||||
for _, rr := range answer {
|
for _, rr := range answer {
|
||||||
@@ -181,18 +234,18 @@ func (netboxdns *NetboxDNS) processExtra(
|
|||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var reqType []string
|
//var reqType []string
|
||||||
switch family {
|
//switch qtype {
|
||||||
case 1:
|
//case 1:
|
||||||
reqType = []string{"A"}
|
// reqType = []string{"A"}
|
||||||
case 2:
|
//case 2:
|
||||||
reqType = []string{"AAAA"}
|
// reqType = []string{"AAAA"}
|
||||||
}
|
//}
|
||||||
records, err := netbox.GetRecordsQuery(
|
records, err := netbox.GetRecordsQuery(
|
||||||
netboxdns.requestClient,
|
netboxdns.requestClient,
|
||||||
&netbox.RecordQuery{
|
&netbox.RecordQuery{
|
||||||
FQDN: strings.TrimSuffix(name, "."),
|
FQDN: strings.TrimSuffix(name, "."),
|
||||||
Type: reqType,
|
Type: []string{dns.TypeToString[qtype]},
|
||||||
Zone: zone,
|
Zone: zone,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -208,12 +261,12 @@ func (netboxdns *NetboxDNS) lookupDirect(
|
|||||||
qname string,
|
qname string,
|
||||||
qtype uint16,
|
qtype uint16,
|
||||||
zone *netbox.Zone,
|
zone *netbox.Zone,
|
||||||
family int,
|
|
||||||
) (*lookupResponse, error) {
|
) (*lookupResponse, error) {
|
||||||
queryTypes := []string{dns.TypeToString[qtype]}
|
queryTypes := []string{dns.TypeToString[qtype]}
|
||||||
if qtype == dns.TypeA || qtype == dns.TypeAAAA {
|
if qtype == dns.TypeA || qtype == dns.TypeAAAA {
|
||||||
queryTypes = append(queryTypes, "CNAME")
|
queryTypes = append(queryTypes, "CNAME")
|
||||||
}
|
}
|
||||||
|
|
||||||
records, err := netbox.GetRecordsQuery(
|
records, err := netbox.GetRecordsQuery(
|
||||||
netboxdns.requestClient,
|
netboxdns.requestClient,
|
||||||
&netbox.RecordQuery{
|
&netbox.RecordQuery{
|
||||||
@@ -225,37 +278,79 @@ func (netboxdns *NetboxDNS) lookupDirect(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// log.Debugf("%v", records)
|
||||||
|
|
||||||
if len(records) > 0 {
|
// allRecords := append([]netbox.Record{}, records...)
|
||||||
answer, err := recordsToRR(records)
|
var allRecords []netbox.Record
|
||||||
|
|
||||||
|
var newRecords []netbox.Record
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
foundCNAME := false
|
||||||
|
// If record data end with ., pass as is
|
||||||
|
// If record data doesn't end with ., append the zone name
|
||||||
|
for i, record := range records {
|
||||||
|
// log.Debugf("%v", record)
|
||||||
|
if record.Type == "CNAME" {
|
||||||
|
foundCNAME = true
|
||||||
|
if record.Value[len(record.Value)-1:] != "." {
|
||||||
|
records[i].Value = strings.Join([]string{record.Value, ".", zone.Name, "."}, "")
|
||||||
|
}
|
||||||
|
// log.Debugf("%v", records[i].Value)
|
||||||
|
newRecordsForCNAME, err := netbox.GetRecordsQuery(
|
||||||
|
netboxdns.requestClient,
|
||||||
|
&netbox.RecordQuery{
|
||||||
|
FQDN: records[i].Value,
|
||||||
|
Type: queryTypes,
|
||||||
|
Zone: zone,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
extraRecords, err := netboxdns.processExtra(answer, zone, family)
|
// log.Debugf("%v", newRecordsForCNAME)
|
||||||
|
|
||||||
|
if len(newRecordsForCNAME) > 0 {
|
||||||
|
newRecords = append(newRecords, newRecordsForCNAME...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// log.Debugf("%v", newRecords)
|
||||||
|
|
||||||
|
allRecords = append(allRecords, records...)
|
||||||
|
records = newRecords
|
||||||
|
newRecords = []netbox.Record{}
|
||||||
|
|
||||||
|
if !foundCNAME {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 20 {
|
||||||
|
return nil, fmt.Errorf("CNAME recursion depth exceeded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//for _, record := range allRecords {
|
||||||
|
// log.Debugf("%v -> %v", record.FQDN, record.Value)
|
||||||
|
//}
|
||||||
|
answer, err := recordsToRR(allRecords)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
extra, err := recordsToRR(extraRecords)
|
|
||||||
if err != nil {
|
log.Debug(answer)
|
||||||
return nil, err
|
if len(answer) > 0 {
|
||||||
}
|
|
||||||
cnames := filterRRByType(answer, dns.TypeCNAME)
|
|
||||||
if qtype == dns.TypeCNAME || len(cnames) > 0 {
|
|
||||||
answer = append(answer, extra...)
|
|
||||||
extra = nil
|
|
||||||
}
|
|
||||||
return &lookupResponse{
|
return &lookupResponse{
|
||||||
Answer: answer,
|
Answer: answer,
|
||||||
Extra: extra,
|
Extra: []dns.RR{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
} else {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (netboxdns *NetboxDNS) lookupDelegate(
|
func (netboxdns *NetboxDNS) lookupDelegate(
|
||||||
qname string,
|
qname string,
|
||||||
zone *netbox.Zone,
|
zone *netbox.Zone,
|
||||||
family int,
|
qtype uint16,
|
||||||
) (*lookupResponse, error) {
|
) (*lookupResponse, error) {
|
||||||
records, err := netbox.GetRecordsQuery(
|
records, err := netbox.GetRecordsQuery(
|
||||||
netboxdns.requestClient,
|
netboxdns.requestClient,
|
||||||
@@ -273,7 +368,7 @@ func (netboxdns *NetboxDNS) lookupDelegate(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
extraRecords, err := netboxdns.processExtra(ns, nil, family)
|
extraRecords, err := netboxdns.processExtra(ns, nil, qtype)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-2
@@ -3,6 +3,7 @@ package netboxdns
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
@@ -57,8 +58,13 @@ func (netboxdns *NetboxDNS) ServeDNS(
|
|||||||
) (int, error) {
|
) (int, error) {
|
||||||
state := request.Request{W: respWriter, Req: reqMsg}
|
state := request.Request{W: respWriter, Req: reqMsg}
|
||||||
qname := state.QName()
|
qname := state.QName()
|
||||||
|
reqIP, err := netip.ParseAddr(state.IP())
|
||||||
|
if err != nil {
|
||||||
|
return dns.RcodeServerFailure, err
|
||||||
|
}
|
||||||
family := state.Family()
|
family := state.Family()
|
||||||
qtype := fixQType(state.QType(), family)
|
qtype := state.QType()
|
||||||
|
// qtype := fixQType(state.QType(), family)
|
||||||
|
|
||||||
// check if plugin is configured to respond to the requested zone
|
// check if plugin is configured to respond to the requested zone
|
||||||
respondingZone := plugin.Zones(netboxdns.zones).Matches(qname)
|
respondingZone := plugin.Zones(netboxdns.zones).Matches(qname)
|
||||||
@@ -66,7 +72,7 @@ func (netboxdns *NetboxDNS) ServeDNS(
|
|||||||
return netboxdns.nextOrFailure(reqContext, respWriter, reqMsg)
|
return netboxdns.nextOrFailure(reqContext, respWriter, reqMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := netboxdns.lookup(qname, qtype, family)
|
response, err := netboxdns.lookup(qname, reqIP, qtype, family)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dns.RcodeServerFailure, err
|
return dns.RcodeServerFailure, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user