Skip to content

Commit

Permalink
Feature: IPv6 Support (#199)
Browse files Browse the repository at this point in the history
* Feature: IPv6 Support

Base support IPv6 and aaaa records.

Todo:
- infoblox support, requires next version of infoblox go client version which is not yet released.

* Change NewRecord interface to return errors

Fix logic error in check
  • Loading branch information
poelzi authored Jul 21, 2021
1 parent e36f83a commit 52b8b64
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 30 deletions.
4 changes: 2 additions & 2 deletions pkg/controller/provider/alicloud/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (this *access) DeleteRecord(r raw.Record, zone provider.DNSHostedZone) erro
return err
}

func (this *access) NewRecord(fqdn, rtype, value string, zone provider.DNSHostedZone, ttl int64) raw.Record {
func (this *access) NewRecord(fqdn, rtype, value string, zone provider.DNSHostedZone, ttl int64) (raw.Record, error) {
rr := GetRR(fqdn, zone.Domain())
return (*Record)(&alidns.Record{RR: rr, Type: rtype, Value: value, DomainName: zone.Domain(), TTL: int(ttl)})
return (*Record)(&alidns.Record{RR: rr, Type: rtype, Value: value, DomainName: zone.Domain(), TTL: int(ttl)}), nil
}
7 changes: 7 additions & 0 deletions pkg/controller/provider/azure/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ func (exec *Execution) buildMappedRecordSet(name string, rset *dns.RecordSet) (b
arecords = append(arecords, azure.ARecord{Ipv4Address: &r.Value})
}
properties.ARecords = &arecords
case dns.RS_AAAA:
recordType = azure.AAAA
aaaarecords := []azure.AaaaRecord{}
for _, r := range rset.Records {
aaaarecords = append(aaaarecords, azure.AaaaRecord{Ipv6Address: &r.Value})
}
properties.AaaaRecords = &aaaarecords
case dns.RS_CNAME:
recordType = azure.CNAME
properties.CnameRecord = &azure.CnameRecord{Cname: &rset.Records[0].Value}
Expand Down
4 changes: 2 additions & 2 deletions pkg/controller/provider/cloudflare/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@ func (this *access) DeleteRecord(r raw.Record, zone provider.DNSHostedZone) erro
return err
}

func (this *access) NewRecord(fqdn, rtype, value string, zone provider.DNSHostedZone, ttl int64) raw.Record {
func (this *access) NewRecord(fqdn, rtype, value string, zone provider.DNSHostedZone, ttl int64) (raw.Record, error) {
return (*Record)(&cloudflare.DNSRecord{
Type: rtype,
Name: fqdn,
Content: value,
TTL: int(ttl),
ZoneID: zone.Id(),
})
}), nil
}

func testTTL(ttl *int) {
Expand Down
5 changes: 4 additions & 1 deletion pkg/controller/provider/infoblox/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package infoblox

import (
"fmt"
"strconv"
"strings"

Expand Down Expand Up @@ -62,7 +63,7 @@ func (this *access) DeleteRecord(r raw.Record, zone provider.DNSHostedZone) erro
return err
}

func (this *access) NewRecord(fqdn string, rtype string, value string, zone provider.DNSHostedZone, ttl int64) (record raw.Record) {
func (this *access) NewRecord(fqdn string, rtype string, value string, zone provider.DNSHostedZone, ttl int64) (record raw.Record, err error) {
switch rtype {
case dns.RS_A:
record = (*RecordA)(ibclient.NewRecordA(ibclient.RecordA{
Expand All @@ -71,6 +72,8 @@ func (this *access) NewRecord(fqdn string, rtype string, value string, zone prov
//Zone: zone.Key(),
View: this.view,
}))
case dns.RS_AAAA:
err = fmt.Errorf("warning: aaaa records not supported on infoblox yet")
case dns.RS_CNAME:
record = (*RecordCNAME)(ibclient.NewRecordCNAME(ibclient.RecordCNAME{
Name: fqdn,
Expand Down
4 changes: 2 additions & 2 deletions pkg/controller/provider/netlify/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,14 @@ func (this *access) DeleteRecord(r raw.Record, zone provider.DNSHostedZone) erro
return err
}

func (this *access) NewRecord(fqdn, rtype, value string, zone provider.DNSHostedZone, ttl int64) raw.Record {
func (this *access) NewRecord(fqdn, rtype, value string, zone provider.DNSHostedZone, ttl int64) (raw.Record, error) {
return (*Record)(&models.DNSRecord{
Type: rtype,
Hostname: fqdn,
Value: value,
TTL: int64(ttl),
DNSZoneID: zone.Id(),
})
}), nil
}

func testTTL(ttl *int) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/provider/openstack/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, cache provider.ZoneC

recordSetHandler := func(recordSet *recordsets.RecordSet) error {
switch recordSet.Type {
case dns.RS_A, dns.RS_CNAME, dns.RS_TXT:
case dns.RS_A, dns.RS_AAAA, dns.RS_CNAME, dns.RS_TXT:
rs := dns.NewRecordSet(recordSet.Type, int64(recordSet.TTL), nil)
for _, record := range recordSet.Records {
value := record
Expand Down
10 changes: 7 additions & 3 deletions pkg/dns/provider/changemodel.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,15 +463,19 @@ func (this *ChangeModel) AddTargets(set *dns.DNSSet, base *dns.DNSSet, provider
ttl := t.GetEntry().TTL()
if t.GetRecordType() == dns.RS_CNAME && len(targets) > 1 {
cnames = append(cnames, t.GetHostName())
addrs, err := lookupHostIPv4(t.GetHostName())
ipv4addrs, ipv6addrs, err := lookupHosts(t.GetHostName())
if err == nil {
for _, addr := range addrs {
for _, addr := range ipv4addrs {
AddRecord(targetsets, dns.RS_A, addr, ttl)
}
for _, addr := range ipv6addrs {
AddRecord(targetsets, dns.RS_AAAA, addr, ttl)
}
} else {
this.Errorf("cannot lookup '%s': %s", t.GetHostName(), err)
}
this.Debugf("mapping target '%s' to A records: %s", t.GetHostName(), strings.Join(addrs, ","))
this.Debugf("mapping target '%s' to A records: %s or AAAA records: %s",
t.GetHostName(), strings.Join(ipv4addrs, ","), strings.Join(ipv6addrs, ","))
} else {
t = provider.MapTarget(t)
AddRecord(targetsets, t.GetRecordType(), t.GetHostName(), ttl)
Expand Down
41 changes: 27 additions & 14 deletions pkg/dns/provider/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,10 @@ func validate(logger logger.LogContext, state *state, entry *EntryVersion, p *En
}
var new Target
new, err = NewTargetFromEntryVersion(t, entry)
// we should ignore unsupported addresses since the user might not influence
// the provider assigned ip address type
if err != nil {
return
continue
}
if targets.Has(new) {
warnings = append(warnings, fmt.Sprintf("dns entry %q has duplicate target %q", entry.ObjectName(), new))
Expand Down Expand Up @@ -655,17 +657,26 @@ func normalizeTargets(logger logger.LogContext, object *dnsutils.DNSEntryObject,
return result, true, false
}
for _, t := range targets {
addrs, err := lookupHostIPv4(t.GetHostName())
ipv4addrs, ipv6addrs, err := lookupHosts(t.GetHostName())
if err == nil {
outer:
for _, addr := range addrs {
outerV4:
for _, addr := range ipv4addrs {
for _, old := range result {
if old.GetHostName() == addr {
continue outer
continue outerV4
}
}
result = append(result, NewTarget(dns.RS_A, addr, t.GetEntry()))
}
outerV6:
for _, addr := range ipv6addrs {
for _, old := range result {
if old.GetHostName() == addr {
continue outerV6
}
}
result = append(result, NewTarget(dns.RS_AAAA, addr, t.GetEntry()))
}
} else {
w := fmt.Sprintf("cannot lookup '%s': %s", t.GetHostName(), err)
logger.Warn(w)
Expand All @@ -675,22 +686,24 @@ func normalizeTargets(logger logger.LogContext, object *dnsutils.DNSEntryObject,
return result, true, true
}

func lookupHostIPv4(hostname string) ([]string, error) {
func lookupHosts(hostname string) ([]string, []string, error) {
ips, err := net.LookupIP(hostname)
if err != nil {
return nil, err
return nil, nil, err
}
addrs := make([]string, 0, len(ips))
ipv4addrs := make([]string, 0, len(ips))
ipv6addrs := make([]string, 0, len(ips))
for _, ip := range ips {
if ip.To4() == nil {
continue
if ip.To4() != nil {
ipv4addrs = append(ipv4addrs, ip.String())
} else if ip.To16() != nil {
ipv6addrs = append(ipv6addrs, ip.String())
}
addrs = append(addrs, ip.String())
}
if len(addrs) == 0 {
return nil, fmt.Errorf("%s has no IPv4 address (of %d addresses)", hostname, len(ips))
if len(ipv4addrs) == 0 && len(ipv6addrs) == 0 {
return nil, nil, fmt.Errorf("%s has no IPv4/IPv6 address (of %d addresses)", hostname, len(ips))
}
return addrs, nil
return ipv4addrs, ipv6addrs, nil
}

///////////////////////////////////////////////////////////////////////////////
Expand Down
9 changes: 7 additions & 2 deletions pkg/dns/provider/raw/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Executor interface {
UpdateRecord(r Record, zone provider.DNSHostedZone) error
DeleteRecord(r Record, zone provider.DNSHostedZone) error

NewRecord(fqdn, rtype, value string, zone provider.DNSHostedZone, ttl int64) Record
NewRecord(fqdn, rtype, value string, zone provider.DNSHostedZone, ttl int64) (Record, error)
}

type result struct {
Expand Down Expand Up @@ -119,7 +119,12 @@ func (this *Execution) add(dnsname string, rset *dns.RecordSet, modonly bool, fo
}
} else {
if notfound != nil {
*notfound = append(*notfound, this.executor.NewRecord(dnsname, rset.Type, r.Value, this.zone, rset.TTL))
record, err := this.executor.NewRecord(dnsname, rset.Type, r.Value, this.zone, rset.TTL)
if err == nil {
*notfound = append(*notfound, record)
} else {
this.Errorf("Error adding record: %s (%s): %s", dnsname, rtype, err)
}
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion pkg/dns/provider/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ package provider

import (
"fmt"
"github.com/gardener/external-dns-management/pkg/dns"
"net"

"github.com/gardener/external-dns-management/pkg/dns"
)

////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -77,6 +78,8 @@ func NewTargetFromEntryVersion(name string, entry *EntryVersion) (Target, error)
return NewTarget(dns.RS_CNAME, name, entry), nil
} else if ip.To4() != nil {
return NewTarget(dns.RS_A, name, entry), nil
} else if ip.To16() != nil {
return NewTarget(dns.RS_AAAA, name, entry), nil
} else {
return nil, fmt.Errorf("IPv6 addresses are not supported yet: %s (%s)", ip.String(), name)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/dns/records.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const RS_ALIAS = "ALIAS" // provider specific alias for CNAME record (e.g. AWS a
const RS_TXT = "TXT"
const RS_CNAME = "CNAME"
const RS_A = "A"
const RS_AAAA = "AAAA"

const RS_NS = "NS"

Expand Down
2 changes: 1 addition & 1 deletion pkg/dns/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (

func SupportedRecordType(t string) bool {
switch t {
case RS_CNAME, RS_A, RS_TXT:
case RS_CNAME, RS_A, RS_AAAA, RS_TXT:
return true
}
return false
Expand Down
63 changes: 62 additions & 1 deletion test/functional/basics.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,29 @@ spec:
---
apiVersion: dns.gardener.cloud/v1alpha1
kind: DNSEntry
metadata:
name: {{.Prefix}}aaaa
namespace: {{.Namespace}}
spec:
dnsName: {{.Prefix}}aaaa.{{.Domain}}
ttl: {{.TTL}}
targets:
- 20a0::1
---
apiVersion: dns.gardener.cloud/v1alpha1
kind: DNSEntry
metadata:
name: {{.Prefix}}mixed
namespace: {{.Namespace}}
spec:
dnsName: {{.Prefix}}mixed.{{.Domain}}
ttl: {{.TTL}}
targets:
- 20a0::2
- 11.11.0.11
---
apiVersion: dns.gardener.cloud/v1alpha1
kind: DNSEntry
metadata:
name: {{.Prefix}}txt
namespace: {{.Namespace}}
Expand Down Expand Up @@ -168,7 +191,7 @@ func functestbasics(cfg *config.Config, p *config.ProviderConfig) {
Ω(err).Should(BeNil())

entryNames := []string{}
for _, name := range []string{"a", "txt", "wildcard", "cname", "cname-multi"} {
for _, name := range []string{"a", "aaaa", "mixed", "txt", "wildcard", "cname", "cname-multi"} {
entryNames = append(entryNames, entryName(p, name))
}
if p.AliasTarget != "" {
Expand Down Expand Up @@ -199,6 +222,42 @@ func functestbasics(cfg *config.Config, p *config.ProviderConfig) {
"zone": Equal(p.ZoneID),
}),
}),
entryName(p, "aaaa"): MatchKeys(IgnoreExtras, Keys{
"metadata": MatchKeys(IgnoreExtras, Keys{
"finalizers": And(HaveLen(1), ContainElement("dns.gardener.cloud/"+p.FinalizerType)),
}),
"spec": MatchKeys(IgnoreExtras, Keys{
"dnsName": Equal(dnsName(p, "aaaa")),
"targets": And(HaveLen(1), ContainElement("20a0::2"), ContainElement("20a0::2")),
}),
"status": MatchKeys(IgnoreExtras, Keys{
"message": Equal("dns entry active"),
"provider": Equal(p.Namespace + "/" + p.Name),
"providerType": Equal(p.Type),
"state": Equal("Ready"),
"targets": And(HaveLen(1), ContainElement("20a0::1")),
"ttl": Equal(float64(ttl)),
"zone": Equal(p.ZoneID),
}),
}),
entryName(p, "mixed"): MatchKeys(IgnoreExtras, Keys{
"metadata": MatchKeys(IgnoreExtras, Keys{
"finalizers": And(HaveLen(1), ContainElement("dns.gardener.cloud/"+p.FinalizerType)),
}),
"spec": MatchKeys(IgnoreExtras, Keys{
"dnsName": Equal(dnsName(p, "aaaa")),
"targets": And(HaveLen(2), ContainElement("20a0::2"), ContainElement("11.11.0.11")),
}),
"status": MatchKeys(IgnoreExtras, Keys{
"message": Equal("dns entry active"),
"provider": Equal(p.Namespace + "/" + p.Name),
"providerType": Equal(p.Type),
"state": Equal("Ready"),
"targets": And(HaveLen(2), ContainElement("20a0::2"), ContainElement("11.11.0.11")),
"ttl": Equal(float64(ttl)),
"zone": Equal(p.ZoneID),
}),
}),
entryName(p, "txt"): MatchKeys(IgnoreExtras, Keys{
"spec": MatchKeys(IgnoreExtras, Keys{
"dnsName": Equal(dnsName(p, "txt")),
Expand Down Expand Up @@ -272,6 +331,8 @@ func functestbasics(cfg *config.Config, p *config.ProviderConfig) {
u.AwaitLookupCName(dnsName(p, "alias"), p.AliasTarget)
}
u.AwaitLookup(dnsName(p, "a"), "11.11.11.11")
u.AwaitLookup(dnsName(p, "aaaa"), "20a0::1")
u.AwaitLookup(dnsName(p, "mixed"), "20a0::2", "11.11.0.11")
randname := config.RandStringBytes(6)
u.AwaitLookup(randname+"."+dnsName(p, "wildcard"), "44.44.44.44")
u.AwaitLookupCName(dnsName(p, "cname"), "google-public-dns-a.google.com")
Expand Down

0 comments on commit 52b8b64

Please sign in to comment.