From e3266d24f7a3a120739875ff421d2d47d044ad69 Mon Sep 17 00:00:00 2001 From: Jonathan Nagy Date: Sat, 4 Jan 2020 00:16:04 +1100 Subject: [PATCH] Resolve TXT records via CNAME (#3557) * Add test case for TXT lookup via CNAME Signed-off-by: Jonathan Nagy * Return HostType of explicit TXT records Signed-off-by: Jonathan Nagy * Adapt TXT method lookup to allow lookup via CNAME Signed-off-by: Jonathan Nagy * Implement lookup of TXT records via CNAME Signed-off-by: Jonathan Nagy --- plugin/backend_lookup.go | 60 ++++++++++++++++++++++++++++++++++-- plugin/etcd/cname_test.go | 12 ++++++++ plugin/etcd/etcd.go | 10 +++--- plugin/etcd/handler.go | 2 +- plugin/etcd/msg/type.go | 6 +++- plugin/kubernetes/handler.go | 2 +- 6 files changed, 80 insertions(+), 12 deletions(-) diff --git a/plugin/backend_lookup.go b/plugin/backend_lookup.go index 9e5c9eeec..c563295d6 100644 --- a/plugin/backend_lookup.go +++ b/plugin/backend_lookup.go @@ -325,15 +325,69 @@ func CNAME(ctx context.Context, b ServiceBackend, zone string, state request.Req } // TXT returns TXT records from Backend or an error. -func TXT(ctx context.Context, b ServiceBackend, zone string, state request.Request, opt Options) (records []dns.RR, err error) { - services, err := b.Services(ctx, state, false, opt) +func TXT(ctx context.Context, b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, err error) { + + services, err := b.Services(ctx, state, true, opt) if err != nil { return nil, err } + dup := make(map[string]struct{}) + for _, serv := range services { - records = append(records, serv.NewTXT(state.QName())) + + what, _ := serv.HostType() + + switch what { + case dns.TypeCNAME: + if Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { + // x CNAME x is a direct loop, don't add those + continue + } + + newRecord := serv.NewCNAME(state.QName(), serv.Host) + if len(previousRecords) > 7 { + // don't add it, and just continue + continue + } + if dnsutil.DuplicateCNAME(newRecord, previousRecords) { + continue + } + if dns.IsSubDomain(zone, dns.Fqdn(serv.Host)) { + state1 := state.NewWithQuestion(serv.Host, state.QType()) + state1.Zone = zone + nextRecords, err := TXT(ctx, b, zone, state1, append(previousRecords, newRecord), opt) + + if err == nil { + // Not only have we found something we should add the CNAME and the IP addresses. + if len(nextRecords) > 0 { + records = append(records, newRecord) + records = append(records, nextRecords...) + } + } + continue + } + // This means we can not complete the CNAME, try to look else where. + target := newRecord.Target + // Lookup + m1, e1 := b.Lookup(ctx, state, target, state.QType()) + if e1 != nil { + continue + } + // Len(m1.Answer) > 0 here is well? + records = append(records, newRecord) + records = append(records, m1.Answer...) + continue + + case dns.TypeTXT: + if _, ok := dup[serv.Host]; !ok { + dup[serv.Host] = struct{}{} + return append(records, serv.NewTXT(state.QName())), nil + } + + } } + return records, nil } diff --git a/plugin/etcd/cname_test.go b/plugin/etcd/cname_test.go index 3d571077f..a7d45d510 100644 --- a/plugin/etcd/cname_test.go +++ b/plugin/etcd/cname_test.go @@ -58,6 +58,9 @@ var servicesCname = []*msg.Service{ {Host: "cname6.region2.skydns.test", Key: "cname5.region2.skydns.test."}, {Host: "endpoint.region2.skydns.test", Key: "cname6.region2.skydns.test."}, {Host: "mainendpoint.region2.skydns.test", Key: "region2.skydns.test."}, + {Host: "cname2.region3.skydns.test", Key: "cname3.region3.skydns.test."}, + {Host: "cname1.region3.skydns.test", Key: "cname2.region3.skydns.test."}, + {Host: "region3.skydns.test", Key: "cname1.region3.skydns.test."}, {Host: "", Key: "region3.skydns.test.", Text: "SOME-RECORD-TEXT"}, {Host: "10.240.0.1", Key: "endpoint.region2.skydns.test."}, } @@ -91,4 +94,13 @@ var dnsTestCasesCname = []test.Case{ test.SOA("skydns.test. 303 IN SOA ns.dns.skydns.test. hostmaster.skydns.test. 1546424605 7200 1800 86400 30"), }, }, + { + Qname: "cname3.region3.skydns.test.", Qtype: dns.TypeTXT, + Answer: []dns.RR{ + test.CNAME("cname3.region3.skydns.test. 300 IN CNAME cname2.region3.skydns.test."), + test.CNAME("cname2.region3.skydns.test. 300 IN CNAME cname1.region3.skydns.test."), + test.CNAME("cname1.region3.skydns.test. 300 IN CNAME region3.skydns.test."), + test.TXT("region3.skydns.test. 300 IN TXT \"SOME-RECORD-TEXT\""), + }, + }, } diff --git a/plugin/etcd/etcd.go b/plugin/etcd/etcd.go index 305e8e492..d8e9e054e 100644 --- a/plugin/etcd/etcd.go +++ b/plugin/etcd/etcd.go @@ -177,11 +177,9 @@ func (e *Etcd) TTL(kv *mvccpb.KeyValue, serv *msg.Service) uint32 { } // shouldInclude returns true if the service should be included in a list of records, given the qType. For all the -// currently supported lookup types, the only one to allow for an empty Host field in the service are TXT records. -// Similarly, the TXT record in turn requires the Text field to be set. +// currently supported lookup types, the only one to allow for an empty Host field in the service are TXT records +// which resolve directly. If a TXT record is being resolved by CNAME, then we expect the Host field to have a +// value while the TXT field will be empty. func shouldInclude(serv *msg.Service, qType uint16) bool { - if qType == dns.TypeTXT { - return serv.Text != "" - } - return serv.Host != "" + return (qType == dns.TypeTXT && serv.Text != "") || serv.Host != "" } diff --git a/plugin/etcd/handler.go b/plugin/etcd/handler.go index c62956736..395199bd0 100644 --- a/plugin/etcd/handler.go +++ b/plugin/etcd/handler.go @@ -30,7 +30,7 @@ func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) ( case dns.TypeAAAA: records, err = plugin.AAAA(ctx, e, zone, state, nil, opt) case dns.TypeTXT: - records, err = plugin.TXT(ctx, e, zone, state, opt) + records, err = plugin.TXT(ctx, e, zone, state, nil, opt) case dns.TypeCNAME: records, err = plugin.CNAME(ctx, e, zone, state, opt) case dns.TypePTR: diff --git a/plugin/etcd/msg/type.go b/plugin/etcd/msg/type.go index 7f3bfdbb9..ad09e74fb 100644 --- a/plugin/etcd/msg/type.go +++ b/plugin/etcd/msg/type.go @@ -19,8 +19,12 @@ func (s *Service) HostType() (what uint16, normalized net.IP) { ip := net.ParseIP(s.Host) switch { + case ip == nil: - return dns.TypeCNAME, nil + if len(s.Text) == 0 { + return dns.TypeCNAME, nil + } + return dns.TypeTXT, nil case ip.To4() != nil: return dns.TypeA, ip.To4() diff --git a/plugin/kubernetes/handler.go b/plugin/kubernetes/handler.go index 6ff8040a4..78761d303 100644 --- a/plugin/kubernetes/handler.go +++ b/plugin/kubernetes/handler.go @@ -35,7 +35,7 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M case dns.TypeAAAA: records, err = plugin.AAAA(ctx, &k, zone, state, nil, plugin.Options{}) case dns.TypeTXT: - records, err = plugin.TXT(ctx, &k, zone, state, plugin.Options{}) + records, err = plugin.TXT(ctx, &k, zone, state, nil, plugin.Options{}) case dns.TypeCNAME: records, err = plugin.CNAME(ctx, &k, zone, state, plugin.Options{}) case dns.TypePTR: