Skip to content

Commit

Permalink
Add TXT record support
Browse files Browse the repository at this point in the history
Signed-off-by: Dinar Valeev <[email protected]>
  • Loading branch information
k0da committed Jun 9, 2022
1 parent 8d3219e commit bd0543f
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 70 deletions.
61 changes: 61 additions & 0 deletions endpoints.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package gateway

import (
"net"

"github.com/oschwald/maxminddb-golang"
endpoint "sigs.k8s.io/external-dns/endpoint"
)

func fetchEndpointTargets(endpoints []*endpoint.Endpoint, host string, ip net.IP) (results []string, ttl endpoint.TTL) {
for _, ep := range endpoints {
if ep.DNSName == host {
ttl = ep.RecordTTL
if ep.Labels["strategy"] == "geoip" {
results = extractGeo(ep, ip)
if len(results) > 0 {
return
}
}
results = ep.Targets
}
}
return
}

func extractGeo(endpoint *endpoint.Endpoint, clientIP net.IP) (result []string) {
db, err := maxminddb.Open("geoip.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()

clientGeo := &geo{}
err = db.Lookup(clientIP, clientGeo)
if err != nil {
return nil
}

if clientGeo.DC == "" {
log.Infof("empty DC %+v", clientGeo)
return result
}

log.Infof("clientDC: %+v", clientGeo)

for _, ip := range endpoint.Targets {
geoData := &geo{}
log.Infof("processing IP %+v", ip)
err = db.Lookup(net.ParseIP(ip), geoData)
if err != nil {
log.Error(err)
continue
}

log.Infof("IP info: %+v", geoData.DC)
if clientGeo.DC == geoData.DC {
result = append(result, ip)
}
}
return result
}
29 changes: 23 additions & 6 deletions gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (

const defaultSvc = "external-dns.kube-system"

type lookupFunc func(indexKey string, clientIP net.IP) ([]net.IP, endpoint.TTL)
type lookupFunc func(indexKey string, clientIP net.IP) ([]string, endpoint.TTL)

type resourceWithIndex struct {
name string
Expand Down Expand Up @@ -112,6 +112,13 @@ func extractEdnsSubnet(msg *dns.Msg) net.IP {
return nil
}

func targetToIP(targets []string) (ips []net.IP) {
for _, ip := range targets {
ips = append(ips, net.ParseIP(ip))
}
return
}

// ServeDNS implements the plugin.Handle interface.
func (gw *Gateway) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
var clientIP net.IP
Expand Down Expand Up @@ -151,7 +158,7 @@ func (gw *Gateway) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
}
}

var addrs []net.IP
var addrs []string
var ttl endpoint.TTL

// Iterate over supported resources and lookup DNS queries
Expand All @@ -178,7 +185,9 @@ func (gw *Gateway) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms

switch state.QType() {
case dns.TypeA:
m.Answer = gw.A(state, addrs, ttl)
m.Answer = gw.A(state, targetToIP(addrs), ttl)
case dns.TypeTXT:
m.Answer = gw.TXT(state, addrs, ttl)
default:
m.Ns = []dns.RR{gw.soa(state)}
}
Expand All @@ -197,7 +206,7 @@ func (gw *Gateway) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
// Name implements the Handler interface.
func (gw *Gateway) Name() string { return thisPlugin }

// A does the A-record lookup in ingress indexer
// A generates dns.RR for A record
func (gw *Gateway) A(state request.Request, results []net.IP, ttl endpoint.TTL) (records []dns.RR) {
dup := make(map[string]struct{})
if !ttl.IsConfigured() {
Expand All @@ -212,6 +221,14 @@ func (gw *Gateway) A(state request.Request, results []net.IP, ttl endpoint.TTL)
return records
}

// TXT generates dns.RR for TXT record
func (gw *Gateway) TXT(state request.Request, results []string, ttl endpoint.TTL) (records []dns.RR) {
if !ttl.IsConfigured() {
ttl = endpoint.TTL(gw.ttlLow)
}
return append(records, &dns.TXT{Hdr: dns.RR_Header{Name: state.Name(), Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: uint32(ttl)}, Txt: results})
}

func (gw *Gateway) SelfAddress(state request.Request) (records []dns.RR) {
// TODO: need to do self-index lookup for that i need
// a) my own namespace - easy
Expand All @@ -224,7 +241,7 @@ func (gw *Gateway) SelfAddress(state request.Request) (records []dns.RR) {
index = defaultSvc
}

var addrs []net.IP
var addrs []string
var ttl endpoint.TTL
for _, resource := range gw.Resources {
addrs, ttl = resource.lookup(index, net.ParseIP(state.IP()))
Expand All @@ -235,7 +252,7 @@ func (gw *Gateway) SelfAddress(state request.Request) (records []dns.RR) {

m := new(dns.Msg)
m.SetReply(state.Req)
return gw.A(state, addrs, ttl)
return gw.A(state, targetToIP(addrs), ttl)
}

// Strips the closing dot unless it's "."
Expand Down
67 changes: 3 additions & 64 deletions kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
endpoint "sigs.k8s.io/external-dns/endpoint"

// "k8s.io/client-go/tools/clientcmd"
"github.com/oschwald/maxminddb-golang"
)

const (
Expand Down Expand Up @@ -163,73 +161,14 @@ func endpointHostnameIndexFunc(obj interface{}) ([]string, error) {
return hostnames, nil
}

func fetchEndpointIPs(endpoints []*endpoint.Endpoint, host string, ip net.IP) (results []net.IP, ttl endpoint.TTL) {
for _, ep := range endpoints {
if ep.DNSName == host {
ttl = ep.RecordTTL
if ep.Labels["strategy"] == "geoip" {
results = extractGeo(ep, ip)
if len(results) > 0 {
return
}
}
results = extractEndpointIPs(ep)
}
}
return
}

func extractEndpointIPs(endpoint *endpoint.Endpoint) (result []net.IP) {
for _, ip := range endpoint.Targets {
result = append(result, net.ParseIP(ip))
}
return result
}
func extractGeo(endpoint *endpoint.Endpoint, clientIP net.IP) (result []net.IP) {
db, err := maxminddb.Open("geoip.mmdb")
if err != nil {
log.Fatal(err)
}
defer db.Close()

clientGeo := &geo{}
err = db.Lookup(clientIP, clientGeo)
if err != nil {
return nil
}

if clientGeo.DC == "" {
log.Infof("empty DC %+v", clientGeo)
return result
}

log.Infof("clientDC: %+v", clientGeo)

for _, ip := range endpoint.Targets {
geoData := &geo{}
log.Infof("processing IP %+v", ip)
err = db.Lookup(net.ParseIP(ip), geoData)
if err != nil {
log.Error(err)
continue
}

log.Infof("IP info: %+v", geoData.DC)
if clientGeo.DC == geoData.DC && geoData.DC != "" {
result = append(result, net.ParseIP(ip))
}
}
return result
}

func lookupEndpointIndex(ctrl cache.SharedIndexInformer) func(string, net.IP) ([]net.IP, endpoint.TTL) {
return func(indexKey string, clientIP net.IP) (result []net.IP, ttl endpoint.TTL) {
func lookupEndpointIndex(ctrl cache.SharedIndexInformer) func(string, net.IP) ([]string, endpoint.TTL) {
return func(indexKey string, clientIP net.IP) (result []string, ttl endpoint.TTL) {

log.Infof("Index key %+v", indexKey)
objs, _ := ctrl.GetIndexer().ByIndex(endpointHostnameIndex, strings.ToLower(indexKey))
for _, obj := range objs {
endpoint := obj.(*endpoint.DNSEndpoint)
result, ttl = fetchEndpointIPs(endpoint.Spec.Endpoints, indexKey, clientIP)
result, ttl = fetchEndpointTargets(endpoint.Spec.Endpoints, indexKey, clientIP)
}

return
Expand Down
13 changes: 13 additions & 0 deletions terratest/example/dnsendpoint_txt.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: coredns-txt
labels:
k8gb.absa.oss/dnstype: local
spec:
endpoints:
- dnsName: txt.example.org
recordType: TXT
recordTTL: 30
targets:
- "foo=bar"
10 changes: 10 additions & 0 deletions terratest/test/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ func TestBasicExample(t *testing.T) {
require.NoError(t, err)
ttlEndpoint, err := filepath.Abs("../example/dnsendpoint_ttl.yaml")
require.NoError(t, err)
txtEndpoint, err := filepath.Abs("../example/dnsendpoint_txt.yaml")
require.NoError(t, err)
brokenEndpoint, err := filepath.Abs("../example/dnsendpoint_broken.yaml")
require.NoError(t, err)

Expand Down Expand Up @@ -120,4 +122,12 @@ func TestBasicExample(t *testing.T) {
assert.Equal(t, dns.RcodeNameError, msg.Rcode)
assert.Equal(t, 0, len(msg.Answer))
})
t.Run("Basic type TXT resolve", func(t *testing.T) {
expectedTXT := []string{"foo=bar"}
k8s.KubectlApply(t, options, txtEndpoint)
msg, err := DigMsg(t, "localhost", 1053, "txt.example.org", dns.TypeTXT)

require.NoError(t, err)
assert.Equal(t, expectedTXT, msg.Answer[0].(*dns.TXT).Txt)
})
}

0 comments on commit bd0543f

Please sign in to comment.