mirror of
				https://github.com/coredns/coredns.git
				synced 2025-11-03 10:43:20 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			185 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package traffic
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"strconv"
 | 
						|
	"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
 | 
						|
	health  bool
 | 
						|
	origins []string
 | 
						|
	loc     []xds.Locality
 | 
						|
 | 
						|
	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
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	m := new(dns.Msg)
 | 
						|
	m.SetReply(r)
 | 
						|
	m.Authoritative = true
 | 
						|
 | 
						|
	sockaddr, ok := t.c.Select(cluster, t.loc, t.health)
 | 
						|
	if !ok {
 | 
						|
		// 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)
 | 
						|
		if strings.HasPrefix(labels[0], "endpoint-") {
 | 
						|
			// recheck if the cluster exist.
 | 
						|
			cluster = labels[1]
 | 
						|
			sockaddr, ok = t.c.Select(cluster, t.loc, t.health)
 | 
						|
			if !ok {
 | 
						|
				m.Ns = soa(state.Zone)
 | 
						|
				m.Rcode = dns.RcodeNameError
 | 
						|
				w.WriteMsg(m)
 | 
						|
				return 0, nil
 | 
						|
			}
 | 
						|
			return t.serveEndpoint(ctx, state, labels[0], cluster)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if sockaddr == nil {
 | 
						|
		log.Debugf("No (healthy) endpoints found for %q", cluster)
 | 
						|
		m.Ns = soa(state.Zone)
 | 
						|
		w.WriteMsg(m)
 | 
						|
		return 0, nil
 | 
						|
	}
 | 
						|
 | 
						|
	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:
 | 
						|
		sockaddrs, _ := t.c.All(cluster, t.loc, t.health)
 | 
						|
		for i, sa := range sockaddrs {
 | 
						|
			target := fmt.Sprintf("endpoint-%d.%s.%s", i, cluster, state.Zone)
 | 
						|
 | 
						|
			m.Answer = append(m.Answer, &dns.SRV{
 | 
						|
				Hdr:      dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: 5},
 | 
						|
				Priority: 100, Weight: 100, Port: sa.Port(), Target: target})
 | 
						|
 | 
						|
			if sa.Address().To4() == nil {
 | 
						|
				m.Extra = []dns.RR{&dns.AAAA{Hdr: dns.RR_Header{Name: target, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 5}, AAAA: sa.Address()}}
 | 
						|
			} 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
 | 
						|
}
 | 
						|
 | 
						|
func (t *Traffic) serveEndpoint(ctx context.Context, state request.Request, endpoint, cluster string) (int, error) {
 | 
						|
	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
 | 
						|
	}
 | 
						|
 | 
						|
	sockaddrs, _ := t.c.All(cluster, t.loc, t.health)
 | 
						|
	if len(sockaddrs) < nr {
 | 
						|
		m.Ns = soa(state.Zone)
 | 
						|
		m.Rcode = dns.RcodeNameError
 | 
						|
		state.W.WriteMsg(m)
 | 
						|
		return 0, nil
 | 
						|
	}
 | 
						|
 | 
						|
	addr := sockaddrs[nr].Address()
 | 
						|
	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
 | 
						|
		}
 | 
						|
		m.Answer = []dns.RR{&dns.AAAA{Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 5}, AAAA: addr}}
 | 
						|
	default:
 | 
						|
		m.Ns = soa(state.Zone)
 | 
						|
	}
 | 
						|
 | 
						|
	state.W.WriteMsg(m)
 | 
						|
	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" }
 |