| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | package traffic
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							|  |  |  | 	"context"
 | 
					
						
							| 
									
										
										
										
											2020-02-05 16:10:58 +01:00
										 |  |  | 	"crypto/tls"
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 	"fmt"
 | 
					
						
							|  |  |  | 	"strconv"
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	"strings"
 | 
					
						
							|  |  |  | 	"time"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin"
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/pkg/dnsutil"
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin/traffic/xds"
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/request"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-14 13:55:19 +02:00
										 |  |  | 	corepb2 "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	"github.com/miekg/dns"
 | 
					
						
							|  |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Traffic is a plugin that load balances according to assignments.
 | 
					
						
							|  |  |  | type Traffic struct {
 | 
					
						
							| 
									
										
										
										
											2020-02-05 16:10:58 +01:00
										 |  |  | 	c         *xds.Client
 | 
					
						
							|  |  |  | 	node      string
 | 
					
						
							| 
									
										
										
										
											2020-03-04 15:38:29 +01:00
										 |  |  | 	mgmt      string
 | 
					
						
							| 
									
										
										
										
											2020-02-05 16:10:58 +01:00
										 |  |  | 	tlsConfig *tls.Config
 | 
					
						
							|  |  |  | 	hosts     []string
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-24 13:34:59 +01:00
										 |  |  | 	id      string
 | 
					
						
							|  |  |  | 	origins []string
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	Next plugin.Handler
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ServeDNS implements the plugin.Handler interface.
 | 
					
						
							|  |  |  | func (t *Traffic) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
 | 
					
						
							|  |  |  | 	state := request.Request{Req: r, W: w}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cluster := ""
 | 
					
						
							|  |  |  | 	for _, o := range t.origins {
 | 
					
						
							|  |  |  | 		if strings.HasSuffix(state.Name(), o) {
 | 
					
						
							|  |  |  | 			cluster, _ = dnsutil.TrimZone(state.Name(), o)
 | 
					
						
							|  |  |  | 			state.Zone = o
 | 
					
						
							|  |  |  | 			break
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	m := new(dns.Msg)
 | 
					
						
							|  |  |  | 	m.SetReply(r)
 | 
					
						
							|  |  |  | 	m.Authoritative = true
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-29 09:17:27 +02:00
										 |  |  | 	healthy := state.QType() != dns.TypeTXT
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 	sockaddr, ok := t.c.Select(cluster, healthy)
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	if !ok {
 | 
					
						
							| 
									
										
										
										
											2020-03-04 15:38:29 +01:00
										 |  |  | 		// ok this cluster doesn't exist, potentially due to extra labels, which may be garbage or legit queries:
 | 
					
						
							|  |  |  | 		// legit is:
 | 
					
						
							|  |  |  | 		// endpoint-N.cluster
 | 
					
						
							| 
									
										
										
										
											2020-01-18 20:12:25 +01:00
										 |  |  | 		labels := dns.SplitDomainName(cluster)
 | 
					
						
							| 
									
										
										
										
											2020-03-04 15:38:29 +01:00
										 |  |  | 		switch len(labels) {
 | 
					
						
							|  |  |  | 		case 2:
 | 
					
						
							|  |  |  | 			if strings.HasPrefix(strings.ToLower(labels[0]), "endpoint-") {
 | 
					
						
							|  |  |  | 				// recheck if the cluster exist.
 | 
					
						
							|  |  |  | 				cluster = labels[1]
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 				sockaddr, ok = t.c.Select(cluster, healthy)
 | 
					
						
							| 
									
										
										
										
											2020-03-04 15:38:29 +01:00
										 |  |  | 				if !ok {
 | 
					
						
							|  |  |  | 					m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 					m.Rcode = dns.RcodeNameError
 | 
					
						
							|  |  |  | 					w.WriteMsg(m)
 | 
					
						
							|  |  |  | 					return 0, nil
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 				return t.serveEndpoint(ctx, state, labels[0], cluster, healthy)
 | 
					
						
							| 
									
										
										
										
											2020-03-04 15:38:29 +01:00
										 |  |  | 			}
 | 
					
						
							|  |  |  | 		default:
 | 
					
						
							|  |  |  | 			m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 			m.Rcode = dns.RcodeNameError
 | 
					
						
							|  |  |  | 			w.WriteMsg(m)
 | 
					
						
							|  |  |  | 			return 0, nil
 | 
					
						
							| 
									
										
										
										
											2020-01-18 20:12:25 +01:00
										 |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 	if sockaddr == nil {
 | 
					
						
							| 
									
										
										
										
											2020-03-27 21:32:32 +01:00
										 |  |  | 		if cluster == t.mgmt {
 | 
					
						
							|  |  |  | 			log.Debugf("No (healthy) endpoints found for management cluster %q", cluster)
 | 
					
						
							|  |  |  | 		} else {
 | 
					
						
							|  |  |  | 			log.Debugf("No (healthy) endpoints found for %q", cluster)
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 		m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 		w.WriteMsg(m)
 | 
					
						
							|  |  |  | 		return 0, nil
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 	switch state.QType() {
 | 
					
						
							|  |  |  | 	case dns.TypeA:
 | 
					
						
							|  |  |  | 		if sockaddr.Address().To4() == nil { // it's an IPv6 address, return nodata in that case.
 | 
					
						
							|  |  |  | 			m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 			break
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		m.Answer = []dns.RR{&dns.A{Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 5}, A: sockaddr.Address()}}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case dns.TypeAAAA:
 | 
					
						
							|  |  |  | 		if sockaddr.Address().To4() != nil { // it's an IPv4 address, return nodata in that case.
 | 
					
						
							|  |  |  | 			m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 			break
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		m.Answer = []dns.RR{&dns.AAAA{Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 5}, AAAA: sockaddr.Address()}}
 | 
					
						
							|  |  |  | 	case dns.TypeSRV:
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 		sockaddrs, _ := t.c.All(cluster, true)
 | 
					
						
							| 
									
										
										
										
											2020-02-03 21:02:57 +01:00
										 |  |  | 		m.Answer = make([]dns.RR, 0, len(sockaddrs))
 | 
					
						
							|  |  |  | 		m.Extra = make([]dns.RR, 0, len(sockaddrs))
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 		for i, sa := range sockaddrs {
 | 
					
						
							|  |  |  | 			target := fmt.Sprintf("endpoint-%d.%s.%s", i, cluster, state.Zone)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			m.Answer = append(m.Answer, &dns.SRV{
 | 
					
						
							| 
									
										
										
										
											2020-01-19 09:00:09 +01:00
										 |  |  | 				Hdr:      dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: 5},
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 				Priority: 100, Weight: 100, Port: sa.Port(), Target: target})
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if sa.Address().To4() == nil {
 | 
					
						
							| 
									
										
										
										
											2020-02-03 21:02:57 +01:00
										 |  |  | 				m.Extra = append(m.Extra, &dns.AAAA{Hdr: dns.RR_Header{Name: target, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 5}, AAAA: sa.Address()})
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 			} else {
 | 
					
						
							| 
									
										
										
										
											2020-02-03 21:02:57 +01:00
										 |  |  | 				m.Extra = append(m.Extra, &dns.A{Hdr: dns.RR_Header{Name: target, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 5}, A: sa.Address()})
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 			}
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 	case dns.TypeTXT:
 | 
					
						
							|  |  |  | 		sockaddrs, _ := t.c.All(cluster, false)
 | 
					
						
							|  |  |  | 		m.Answer = make([]dns.RR, 0, len(sockaddrs))
 | 
					
						
							|  |  |  | 		m.Extra = make([]dns.RR, 0, len(sockaddrs))
 | 
					
						
							|  |  |  | 		for i, sa := range sockaddrs {
 | 
					
						
							|  |  |  | 			target := fmt.Sprintf("endpoint-%d.%s.%s", i, cluster, state.Zone)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			m.Answer = append(m.Answer, &dns.TXT{
 | 
					
						
							|  |  |  | 				Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 5},
 | 
					
						
							| 
									
										
										
										
											2020-07-14 13:55:19 +02:00
										 |  |  | 				Txt: []string{"100", "100", strconv.Itoa(int(sa.Port())), target, corepb2.HealthStatus_name[int32(sa.Health)]}})
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 			m.Extra = append(m.Extra, &dns.TXT{Hdr: dns.RR_Header{Name: target, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 5}, Txt: []string{sa.Address().String()}})
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 	default:
 | 
					
						
							|  |  |  | 		m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	w.WriteMsg(m)
 | 
					
						
							|  |  |  | 	return 0, nil
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | func (t *Traffic) serveEndpoint(ctx context.Context, state request.Request, endpoint, cluster string, healthy bool) (int, error) {
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 	m := new(dns.Msg)
 | 
					
						
							|  |  |  | 	m.SetReply(state.Req)
 | 
					
						
							|  |  |  | 	m.Authoritative = true
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// get endpoint number
 | 
					
						
							|  |  |  | 	i := strings.Index(endpoint, "-")
 | 
					
						
							|  |  |  | 	if i == -1 || i == len(endpoint) {
 | 
					
						
							|  |  |  | 		m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 		m.Rcode = dns.RcodeNameError
 | 
					
						
							|  |  |  | 		state.W.WriteMsg(m)
 | 
					
						
							|  |  |  | 		return 0, nil
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	end := endpoint[i+1:] // +1 to remove '-'
 | 
					
						
							|  |  |  | 	nr, err := strconv.Atoi(end)
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 		m.Rcode = dns.RcodeNameError
 | 
					
						
							|  |  |  | 		state.W.WriteMsg(m)
 | 
					
						
							|  |  |  | 		return 0, nil
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-06 09:13:27 +01:00
										 |  |  | 	sockaddrs, _ := t.c.All(cluster, healthy)
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 	if len(sockaddrs) < nr {
 | 
					
						
							|  |  |  | 		m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 		m.Rcode = dns.RcodeNameError
 | 
					
						
							|  |  |  | 		state.W.WriteMsg(m)
 | 
					
						
							|  |  |  | 		return 0, nil
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	addr := sockaddrs[nr].Address()
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	switch state.QType() {
 | 
					
						
							|  |  |  | 	case dns.TypeA:
 | 
					
						
							|  |  |  | 		if addr.To4() == nil { // it's an IPv6 address, return nodata in that case.
 | 
					
						
							|  |  |  | 			m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 			break
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		m.Answer = []dns.RR{&dns.A{Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 5}, A: addr}}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case dns.TypeAAAA:
 | 
					
						
							|  |  |  | 		if addr.To4() != nil { // it's an IPv4 address, return nodata in that case.
 | 
					
						
							|  |  |  | 			m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 			break
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2020-01-18 20:12:25 +01:00
										 |  |  | 		m.Answer = []dns.RR{&dns.AAAA{Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 5}, AAAA: addr}}
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	default:
 | 
					
						
							|  |  |  | 		m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 	state.W.WriteMsg(m)
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	return 0, nil
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-04 11:55:42 +01:00
										 |  |  | // Name implements the plugin.Handler interface.
 | 
					
						
							|  |  |  | func (t *Traffic) Name() string { return "traffic" }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | // soa returns a synthetic so for this zone.
 | 
					
						
							|  |  |  | func soa(z string) []dns.RR {
 | 
					
						
							|  |  |  | 	return []dns.RR{&dns.SOA{
 | 
					
						
							|  |  |  | 		Hdr:     dns.RR_Header{Name: z, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 5},
 | 
					
						
							|  |  |  | 		Ns:      dnsutil.Join("ns", z),
 | 
					
						
							|  |  |  | 		Mbox:    dnsutil.Join("coredns", z),
 | 
					
						
							|  |  |  | 		Serial:  uint32(time.Now().UTC().Unix()),
 | 
					
						
							|  |  |  | 		Refresh: 14400,
 | 
					
						
							|  |  |  | 		Retry:   3600,
 | 
					
						
							|  |  |  | 		Expire:  604800,
 | 
					
						
							|  |  |  | 		Minttl:  5,
 | 
					
						
							|  |  |  | 	}}
 | 
					
						
							|  |  |  | }
 |