| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | package traffic
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							|  |  |  | 	"context"
 | 
					
						
							| 
									
										
										
										
											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"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/miekg/dns"
 | 
					
						
							|  |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Traffic is a plugin that load balances according to assignments.
 | 
					
						
							|  |  |  | type Traffic struct {
 | 
					
						
							|  |  |  | 	c       *xds.Client
 | 
					
						
							|  |  |  | 	id      string
 | 
					
						
							| 
									
										
										
										
											2020-01-19 09:14:09 +01:00
										 |  |  | 	health  bool
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	origins []string
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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-01-19 09:14:09 +01:00
										 |  |  | 	sockaddr, ok := t.c.Select(cluster, t.health)
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 	if !ok {
 | 
					
						
							| 
									
										
										
										
											2020-01-18 20:12:25 +01:00
										 |  |  | 		// ok the cluster (which has potentially extra labels), doesn't exist, but we may have a query for endpoint-0.<cluster>.
 | 
					
						
							|  |  |  | 		// check if we have 2 labels and that the first equals endpoint-0.
 | 
					
						
							|  |  |  | 		if dns.CountLabel(cluster) != 2 {
 | 
					
						
							|  |  |  | 			m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 			m.Rcode = dns.RcodeNameError
 | 
					
						
							|  |  |  | 			w.WriteMsg(m)
 | 
					
						
							|  |  |  | 			return 0, nil
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		labels := dns.SplitDomainName(cluster)
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 		if strings.HasPrefix(labels[0], "endpoint-") {
 | 
					
						
							| 
									
										
										
										
											2020-01-18 20:12:25 +01:00
										 |  |  | 			// recheck if the cluster exist.
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:30:13 +01:00
										 |  |  | 			cluster = labels[1]
 | 
					
						
							| 
									
										
										
										
											2020-01-19 09:14:09 +01:00
										 |  |  | 			sockaddr, ok = t.c.Select(cluster, t.health)
 | 
					
						
							| 
									
										
										
										
											2020-01-18 20:12:25 +01:00
										 |  |  | 			if !ok {
 | 
					
						
							|  |  |  | 				m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 				m.Rcode = dns.RcodeNameError
 | 
					
						
							|  |  |  | 				w.WriteMsg(m)
 | 
					
						
							|  |  |  | 				return 0, nil
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:31:18 +01:00
										 |  |  | 			return t.serveEndpoint(ctx, state, labels[0], cluster)
 | 
					
						
							| 
									
										
										
										
											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 {
 | 
					
						
							| 
									
										
										
										
											2019-10-05 11:45:45 +01:00
										 |  |  | 		log.Debugf("No (healthy) endpoints found for %q", cluster)
 | 
					
						
							|  |  |  | 		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-01-19 09:14:09 +01:00
										 |  |  | 		sockaddrs, _ := t.c.All(cluster, t.health)
 | 
					
						
							| 
									
										
										
										
											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-01-19 09:00:09 +01:00
										 |  |  | 				m.Extra = []dns.RR{&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 {
 | 
					
						
							|  |  |  | 				m.Extra = []dns.RR{&dns.A{Hdr: dns.RR_Header{Name: target, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 5}, A: sa.Address()}}
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	default:
 | 
					
						
							|  |  |  | 		m.Ns = soa(state.Zone)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	w.WriteMsg(m)
 | 
					
						
							|  |  |  | 	return 0, nil
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-19 08:31:18 +01:00
										 |  |  | func (t *Traffic) serveEndpoint(ctx context.Context, state request.Request, endpoint, cluster string) (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-01-19 09:14:09 +01:00
										 |  |  | 	sockaddrs, _ := t.c.All(cluster, t.health)
 | 
					
						
							| 
									
										
										
										
											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
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 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,
 | 
					
						
							|  |  |  | 	}}
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Name implements the plugin.Handler interface.
 | 
					
						
							|  |  |  | func (t *Traffic) Name() string { return "traffic" }
 |