Files
coredns/plugin/traffic/traffic.go
Miek Gieben 9c75947393 golint
Signed-off-by: Miek Gieben <miek@miek.nl>
2020-01-19 08:31:18 +01:00

183 lines
4.9 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
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
}
}
m := new(dns.Msg)
m.SetReply(r)
m.Authoritative = true
sockaddr, ok := t.c.Select(cluster)
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)
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)
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.TypeA, 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.TypeA, 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)
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" }