mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 08:14:18 -04:00
This reverts commit 68f1dd5ddf.
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
@@ -40,6 +40,7 @@ kubernetes [ZONES...] {
|
||||
endpoint_pod_names
|
||||
ttl TTL
|
||||
noendpoints
|
||||
transfer to ADDRESS...
|
||||
fallthrough [ZONES...]
|
||||
ignore empty_service
|
||||
}
|
||||
@@ -89,6 +90,11 @@ kubernetes [ZONES...] {
|
||||
0 seconds, and the maximum is capped at 3600 seconds. Setting TTL to 0 will prevent records from being cached.
|
||||
* `noendpoints` will turn off the serving of endpoint records by disabling the watch on endpoints.
|
||||
All endpoint queries and headless service queries will result in an NXDOMAIN.
|
||||
* `transfer` enables zone transfers. It may be specified multiples times. `To` signals the direction
|
||||
(only `to` is allowed). **ADDRESS** must be denoted in CIDR notation (127.0.0.1/32 etc.) or just as
|
||||
plain addresses. The special wildcard `*` means: the entire internet.
|
||||
Sending DNS notifies is not supported.
|
||||
[Deprecated](https://github.com/kubernetes/dns/blob/master/docs/specification.md#26---deprecated-records) pod records in the subdomain `pod.cluster.local` are not transferred.
|
||||
* `fallthrough` **[ZONES...]** If a query for a record in the zones for which the plugin is authoritative
|
||||
results in NXDOMAIN, normally that is what the response will be. However, if you specify this option,
|
||||
the query will instead be passed on down the plugin chain, which can include another plugin to handle
|
||||
@@ -99,8 +105,6 @@ kubernetes [ZONES...] {
|
||||
This allows the querying pod to continue searching for the service in the search path.
|
||||
The search path could, for example, include another Kubernetes cluster.
|
||||
|
||||
Enabling zone transfer is done by using the *transfer* plugin.
|
||||
|
||||
## Ready
|
||||
|
||||
This plugin reports readiness to the ready plugin. This will happen after it has synced to the
|
||||
@@ -234,8 +238,3 @@ If monitoring is enabled (via the *prometheus* plugin) then the following metric
|
||||
## Bugs
|
||||
|
||||
The duration metric only supports the "headless\_with\_selector" service currently.
|
||||
|
||||
## Also See
|
||||
|
||||
See the *autopath* plugin to enable search path optimizations. And use the *transfer* plugin to
|
||||
enable outgoing zone transfers.
|
||||
|
||||
@@ -28,6 +28,8 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
|
||||
)
|
||||
|
||||
switch state.QType() {
|
||||
case dns.TypeAXFR, dns.TypeIXFR:
|
||||
k.Transfer(ctx, state)
|
||||
case dns.TypeA:
|
||||
records, err = plugin.A(ctx, &k, zone, state, nil, plugin.Options{})
|
||||
case dns.TypeAAAA:
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coredns/coredns/plugin/kubernetes/object"
|
||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
||||
@@ -524,7 +525,7 @@ func (APIConnServeTest) Run() {}
|
||||
func (APIConnServeTest) Stop() error { return nil }
|
||||
func (APIConnServeTest) EpIndexReverse(string) []*object.Endpoints { return nil }
|
||||
func (APIConnServeTest) SvcIndexReverse(string) []*object.Service { return nil }
|
||||
func (APIConnServeTest) Modified() int64 { return int64(3) }
|
||||
func (APIConnServeTest) Modified() int64 { return time.Now().Unix() }
|
||||
|
||||
func (APIConnServeTest) PodIndex(ip string) []*object.Pod {
|
||||
if ip != "10.240.0.1" {
|
||||
|
||||
@@ -46,6 +46,7 @@ type Kubernetes struct {
|
||||
primaryZoneIndex int
|
||||
localIPs []net.IP
|
||||
autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath.
|
||||
TransferTo []string
|
||||
}
|
||||
|
||||
// New returns a initialized Kubernetes. It default interfaceAddrFunc to return 127.0.0.1. All other
|
||||
@@ -494,12 +495,6 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
|
||||
return services, err
|
||||
}
|
||||
|
||||
// Serial return the SOA serial.
|
||||
func (k *Kubernetes) Serial(state request.Request) uint32 { return uint32(k.APIConn.Modified()) }
|
||||
|
||||
// MinTTL returns the minimal TTL.
|
||||
func (k *Kubernetes) MinTTL(state request.Request) uint32 { return k.ttl }
|
||||
|
||||
// match checks if a and b are equal taking wildcards into account.
|
||||
func match(a, b string) bool {
|
||||
if wildcard(a) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/coredns/coredns/plugin/metrics"
|
||||
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
||||
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||
"github.com/coredns/coredns/plugin/pkg/parse"
|
||||
"github.com/coredns/coredns/plugin/pkg/upstream"
|
||||
|
||||
"github.com/caddyserver/caddy"
|
||||
@@ -240,6 +241,15 @@ func ParseStanza(c *caddy.Controller) (*Kubernetes, error) {
|
||||
return nil, c.Errf("ttl must be in range [0, 3600]: %d", t)
|
||||
}
|
||||
k8s.ttl = uint32(t)
|
||||
case "transfer":
|
||||
tos, froms, err := parse.Transfer(c, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(froms) != 0 {
|
||||
return nil, c.Errf("transfer from is not supported with this plugin")
|
||||
}
|
||||
k8s.TransferTo = tos
|
||||
case "noendpoints":
|
||||
if len(c.RemainingArgs()) != 0 {
|
||||
return nil, c.ArgErr()
|
||||
|
||||
47
plugin/kubernetes/setup_transfer_test.go
Normal file
47
plugin/kubernetes/setup_transfer_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy"
|
||||
)
|
||||
|
||||
func TestKubernetesParseTransfer(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string // Corefile data as string
|
||||
expected string
|
||||
shouldErr bool
|
||||
}{
|
||||
{`kubernetes cluster.local {
|
||||
transfer to 1.2.3.4
|
||||
}`, "1.2.3.4:53", false},
|
||||
{`kubernetes cluster.local {
|
||||
transfer to 1.2.3.4:53
|
||||
}`, "1.2.3.4:53", false},
|
||||
{`kubernetes cluster.local {
|
||||
transfer to *
|
||||
}`, "*", false},
|
||||
{`kubernetes cluster.local {
|
||||
transfer
|
||||
}`, "", true},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
c := caddy.NewTestController("dns", tc.input)
|
||||
k, err := kubernetesParse(c)
|
||||
if err != nil && !tc.shouldErr {
|
||||
t.Fatalf("Test %d: Expected no error, got %q", i, err)
|
||||
}
|
||||
if err == nil && tc.shouldErr {
|
||||
t.Fatalf("Test %d: Expected error, got none", i)
|
||||
}
|
||||
if err != nil && tc.shouldErr {
|
||||
// input should error
|
||||
continue
|
||||
}
|
||||
|
||||
if k.TransferTo[0] != tc.expected {
|
||||
t.Errorf("Test %d: Expected Transfer To to be %s, got %s", i, tc.expected, k.TransferTo[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,151 +4,207 @@ import (
|
||||
"context"
|
||||
"math"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/coredns/coredns/plugin"
|
||||
"github.com/coredns/coredns/plugin/etcd/msg"
|
||||
"github.com/coredns/coredns/plugin/transfer"
|
||||
"github.com/coredns/coredns/request"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
api "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Transfer implements the transfer.Transfer interface.
|
||||
func (k *Kubernetes) Transfer(zone string, serial uint32) (<-chan []dns.RR, error) {
|
||||
// state is not used here, hence the empty request.Request{]
|
||||
soa, err := plugin.SOA(context.TODO(), k, zone, request.Request{}, plugin.Options{})
|
||||
if err != nil {
|
||||
return nil, transfer.ErrNotAuthoritative
|
||||
const transferLength = 2000
|
||||
|
||||
// Serial implements the Transferer interface.
|
||||
func (k *Kubernetes) Serial(state request.Request) uint32 { return uint32(k.APIConn.Modified()) }
|
||||
|
||||
// MinTTL implements the Transferer interface.
|
||||
func (k *Kubernetes) MinTTL(state request.Request) uint32 { return k.ttl }
|
||||
|
||||
// Transfer implements the Transferer interface.
|
||||
func (k *Kubernetes) Transfer(ctx context.Context, state request.Request) (int, error) {
|
||||
|
||||
if !k.transferAllowed(state) {
|
||||
return dns.RcodeRefused, nil
|
||||
}
|
||||
|
||||
ch := make(chan []dns.RR)
|
||||
// Get all services.
|
||||
rrs := make(chan dns.RR)
|
||||
go k.transfer(rrs, state.Zone)
|
||||
|
||||
records := []dns.RR{}
|
||||
for r := range rrs {
|
||||
records = append(records, r)
|
||||
}
|
||||
|
||||
if len(records) == 0 {
|
||||
return dns.RcodeServerFailure, nil
|
||||
}
|
||||
|
||||
ch := make(chan *dns.Envelope)
|
||||
tr := new(dns.Transfer)
|
||||
|
||||
soa, err := plugin.SOA(ctx, k, state.Zone, state, plugin.Options{})
|
||||
if err != nil {
|
||||
return dns.RcodeServerFailure, nil
|
||||
}
|
||||
|
||||
records = append(soa, records...)
|
||||
records = append(records, soa...)
|
||||
go func(ch chan *dns.Envelope) {
|
||||
j, l := 0, 0
|
||||
log.Infof("Outgoing transfer of %d records of zone %s to %s started", len(records), state.Zone, state.IP())
|
||||
for i, r := range records {
|
||||
l += dns.Len(r)
|
||||
if l > transferLength {
|
||||
ch <- &dns.Envelope{RR: records[j:i]}
|
||||
l = 0
|
||||
j = i
|
||||
}
|
||||
}
|
||||
if j < len(records) {
|
||||
ch <- &dns.Envelope{RR: records[j:]}
|
||||
}
|
||||
close(ch)
|
||||
}(ch)
|
||||
|
||||
tr.Out(state.W, state.Req, ch)
|
||||
// Defer closing to the client
|
||||
state.W.Hijack()
|
||||
return dns.RcodeSuccess, nil
|
||||
}
|
||||
|
||||
// transferAllowed checks if incoming request for transferring the zone is allowed according to the ACLs.
|
||||
// Note: This is copied from zone.transferAllowed, but should eventually be factored into a common transfer pkg.
|
||||
func (k *Kubernetes) transferAllowed(state request.Request) bool {
|
||||
for _, t := range k.TransferTo {
|
||||
if t == "*" {
|
||||
return true
|
||||
}
|
||||
// If remote IP matches we accept.
|
||||
remote := state.IP()
|
||||
to, _, err := net.SplitHostPort(t)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if to == remote {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (k *Kubernetes) transfer(c chan dns.RR, zone string) {
|
||||
|
||||
defer close(c)
|
||||
|
||||
zonePath := msg.Path(zone, "coredns")
|
||||
serviceList := k.APIConn.ServiceList()
|
||||
|
||||
go func() {
|
||||
// ixfr fallback
|
||||
if serial != 0 && soa[0].(*dns.SOA).Serial == serial {
|
||||
ch <- soa
|
||||
close(ch)
|
||||
return
|
||||
for _, svc := range serviceList {
|
||||
if !k.namespaceExposed(svc.Namespace) {
|
||||
continue
|
||||
}
|
||||
ch <- soa
|
||||
svcBase := []string{zonePath, Svc, svc.Namespace, svc.Name}
|
||||
switch svc.Type {
|
||||
case api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer:
|
||||
clusterIP := net.ParseIP(svc.ClusterIP)
|
||||
if clusterIP != nil {
|
||||
s := msg.Service{Host: svc.ClusterIP, TTL: k.ttl}
|
||||
s.Key = strings.Join(svcBase, "/")
|
||||
|
||||
sort.Slice(serviceList, func(i, j int) bool {
|
||||
return serviceList[i].Name < serviceList[j].Name
|
||||
})
|
||||
// Change host from IP to Name for SRV records
|
||||
host := emitAddressRecord(c, s)
|
||||
|
||||
for _, svc := range serviceList {
|
||||
if !k.namespaceExposed(svc.Namespace) {
|
||||
continue
|
||||
}
|
||||
svcBase := []string{zonePath, Svc, svc.Namespace, svc.Name}
|
||||
switch svc.Type {
|
||||
|
||||
case api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer:
|
||||
clusterIP := net.ParseIP(svc.ClusterIP)
|
||||
if clusterIP != nil {
|
||||
s := msg.Service{Host: svc.ClusterIP, TTL: k.ttl}
|
||||
for _, p := range svc.Ports {
|
||||
s := msg.Service{Host: host, Port: int(p.Port), TTL: k.ttl}
|
||||
s.Key = strings.Join(svcBase, "/")
|
||||
|
||||
// Change host from IP to Name for SRV records
|
||||
host := emitAddressRecord(ch, s)
|
||||
// Need to generate this to handle use cases for peer-finder
|
||||
// ref: https://github.com/coredns/coredns/pull/823
|
||||
c <- s.NewSRV(msg.Domain(s.Key), 100)
|
||||
|
||||
for _, p := range svc.Ports {
|
||||
s := msg.Service{Host: host, Port: int(p.Port), TTL: k.ttl}
|
||||
s.Key = strings.Join(svcBase, "/")
|
||||
|
||||
// Need to generate this to handle use cases for peer-finder
|
||||
// ref: https://github.com/coredns/coredns/pull/823
|
||||
ch <- []dns.RR{s.NewSRV(msg.Domain(s.Key), 100)}
|
||||
|
||||
// As per spec unnamed ports do not have a srv record
|
||||
// https://github.com/kubernetes/dns/blob/master/docs/specification.md#232---srv-records
|
||||
if p.Name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
s.Key = strings.Join(append(svcBase, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/")
|
||||
|
||||
ch <- []dns.RR{s.NewSRV(msg.Domain(s.Key), 100)}
|
||||
}
|
||||
|
||||
// Skip endpoint discovery if clusterIP is defined
|
||||
continue
|
||||
}
|
||||
|
||||
endpointsList := k.APIConn.EpIndex(svc.Name + "." + svc.Namespace)
|
||||
|
||||
for _, ep := range endpointsList {
|
||||
if ep.Name != svc.Name || ep.Namespace != svc.Namespace {
|
||||
// As per spec unnamed ports do not have a srv record
|
||||
// https://github.com/kubernetes/dns/blob/master/docs/specification.md#232---srv-records
|
||||
if p.Name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, eps := range ep.Subsets {
|
||||
srvWeight := calcSRVWeight(len(eps.Addresses))
|
||||
for _, addr := range eps.Addresses {
|
||||
s := msg.Service{Host: addr.IP, TTL: k.ttl}
|
||||
s.Key = strings.Join(svcBase, "/")
|
||||
// We don't need to change the msg.Service host from IP to Name yet
|
||||
// so disregard the return value here
|
||||
emitAddressRecord(ch, s)
|
||||
s.Key = strings.Join(append(svcBase, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/")
|
||||
|
||||
s.Key = strings.Join(append(svcBase, endpointHostname(addr, k.endpointNameMode)), "/")
|
||||
// Change host from IP to Name for SRV records
|
||||
host := emitAddressRecord(ch, s)
|
||||
s.Host = host
|
||||
c <- s.NewSRV(msg.Domain(s.Key), 100)
|
||||
}
|
||||
|
||||
for _, p := range eps.Ports {
|
||||
// As per spec unnamed ports do not have a srv record
|
||||
// https://github.com/kubernetes/dns/blob/master/docs/specification.md#232---srv-records
|
||||
if p.Name == "" {
|
||||
continue
|
||||
}
|
||||
// Skip endpoint discovery if clusterIP is defined
|
||||
continue
|
||||
}
|
||||
|
||||
s.Port = int(p.Port)
|
||||
endpointsList := k.APIConn.EpIndex(svc.Name + "." + svc.Namespace)
|
||||
|
||||
s.Key = strings.Join(append(svcBase, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/")
|
||||
ch <- []dns.RR{s.NewSRV(msg.Domain(s.Key), srvWeight)}
|
||||
for _, ep := range endpointsList {
|
||||
if ep.Name != svc.Name || ep.Namespace != svc.Namespace {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, eps := range ep.Subsets {
|
||||
srvWeight := calcSRVWeight(len(eps.Addresses))
|
||||
for _, addr := range eps.Addresses {
|
||||
s := msg.Service{Host: addr.IP, TTL: k.ttl}
|
||||
s.Key = strings.Join(svcBase, "/")
|
||||
// We don't need to change the msg.Service host from IP to Name yet
|
||||
// so disregard the return value here
|
||||
emitAddressRecord(c, s)
|
||||
|
||||
s.Key = strings.Join(append(svcBase, endpointHostname(addr, k.endpointNameMode)), "/")
|
||||
// Change host from IP to Name for SRV records
|
||||
host := emitAddressRecord(c, s)
|
||||
s.Host = host
|
||||
|
||||
for _, p := range eps.Ports {
|
||||
// As per spec unnamed ports do not have a srv record
|
||||
// https://github.com/kubernetes/dns/blob/master/docs/specification.md#232---srv-records
|
||||
if p.Name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
s.Port = int(p.Port)
|
||||
|
||||
s.Key = strings.Join(append(svcBase, strings.ToLower("_"+string(p.Protocol)), strings.ToLower("_"+string(p.Name))), "/")
|
||||
c <- s.NewSRV(msg.Domain(s.Key), srvWeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case api.ServiceTypeExternalName:
|
||||
case api.ServiceTypeExternalName:
|
||||
|
||||
s := msg.Service{Key: strings.Join(svcBase, "/"), Host: svc.ExternalName, TTL: k.ttl}
|
||||
if t, _ := s.HostType(); t == dns.TypeCNAME {
|
||||
ch <- []dns.RR{s.NewCNAME(msg.Domain(s.Key), s.Host)}
|
||||
}
|
||||
s := msg.Service{Key: strings.Join(svcBase, "/"), Host: svc.ExternalName, TTL: k.ttl}
|
||||
if t, _ := s.HostType(); t == dns.TypeCNAME {
|
||||
c <- s.NewCNAME(msg.Domain(s.Key), s.Host)
|
||||
}
|
||||
}
|
||||
ch <- soa
|
||||
close(ch)
|
||||
}()
|
||||
return ch, nil
|
||||
}
|
||||
}
|
||||
|
||||
// emitAddressRecord generates a new A or AAAA record based on the msg.Service and writes it to a channel.
|
||||
// emitAddressRecord generates a new A or AAAA record based on the msg.Service and writes it to
|
||||
// a channel.
|
||||
// emitAddressRecord returns the host name from the generated record.
|
||||
func emitAddressRecord(c chan<- []dns.RR, s msg.Service) string {
|
||||
ip := net.ParseIP(s.Host)
|
||||
dnsType, _ := s.HostType()
|
||||
func emitAddressRecord(c chan dns.RR, message msg.Service) string {
|
||||
ip := net.ParseIP(message.Host)
|
||||
var host string
|
||||
dnsType, _ := message.HostType()
|
||||
switch dnsType {
|
||||
case dns.TypeA:
|
||||
r := s.NewA(msg.Domain(s.Key), ip)
|
||||
c <- []dns.RR{r}
|
||||
return r.Hdr.Name
|
||||
arec := message.NewA(msg.Domain(message.Key), ip)
|
||||
host = arec.Hdr.Name
|
||||
c <- arec
|
||||
case dns.TypeAAAA:
|
||||
r := s.NewAAAA(msg.Domain(s.Key), ip)
|
||||
c <- []dns.RR{r}
|
||||
return r.Hdr.Name
|
||||
arec := message.NewAAAA(msg.Domain(message.Key), ip)
|
||||
host = arec.Hdr.Name
|
||||
c <- arec
|
||||
}
|
||||
|
||||
return ""
|
||||
return host
|
||||
}
|
||||
|
||||
// calcSrvWeight borrows the logic implemented in plugin.SRV for dynamically
|
||||
|
||||
@@ -1,126 +1,229 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/coredns/plugin/kubernetes/object"
|
||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
||||
"github.com/coredns/coredns/plugin/test"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func TestKubernetesAXFR(t *testing.T) {
|
||||
func TestKubernetesXFR(t *testing.T) {
|
||||
k := New([]string{"cluster.local."})
|
||||
k.APIConn = &APIConnServeTest{}
|
||||
k.TransferTo = []string{"10.240.0.1:53"}
|
||||
k.Namespaces = map[string]struct{}{"testns": {}}
|
||||
|
||||
ctx := context.TODO()
|
||||
w := dnstest.NewMultiRecorder(&test.ResponseWriter{})
|
||||
dnsmsg := &dns.Msg{}
|
||||
dnsmsg.SetAxfr(k.Zones[0])
|
||||
|
||||
ch, err := k.Transfer(k.Zones[0], 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
validateAXFR(t, ch)
|
||||
}
|
||||
|
||||
func TestKubernetesIXFRFallback(t *testing.T) {
|
||||
k := New([]string{"cluster.local."})
|
||||
k.APIConn = &APIConnServeTest{}
|
||||
k.Namespaces = map[string]struct{}{"testns": {}}
|
||||
|
||||
dnsmsg := &dns.Msg{}
|
||||
dnsmsg.SetAxfr(k.Zones[0])
|
||||
|
||||
ch, err := k.Transfer(k.Zones[0], 1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
validateAXFR(t, ch)
|
||||
}
|
||||
|
||||
func TestKubernetesIXFRCurrent(t *testing.T) {
|
||||
k := New([]string{"cluster.local."})
|
||||
k.APIConn = &APIConnServeTest{}
|
||||
k.Namespaces = map[string]struct{}{"testns": {}}
|
||||
|
||||
dnsmsg := &dns.Msg{}
|
||||
dnsmsg.SetAxfr(k.Zones[0])
|
||||
|
||||
ch, err := k.Transfer(k.Zones[0], 3)
|
||||
_, err := k.ServeDNS(ctx, w, dnsmsg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var gotRRs []dns.RR
|
||||
for rrs := range ch {
|
||||
gotRRs = append(gotRRs, rrs...)
|
||||
if len(w.Msgs) == 0 {
|
||||
t.Logf("%+v\n", w)
|
||||
t.Fatal("Did not get back a zone response")
|
||||
}
|
||||
|
||||
// ensure only one record is returned
|
||||
if len(gotRRs) > 1 {
|
||||
t.Errorf("Expected only one answer, got %d", len(gotRRs))
|
||||
if len(w.Msgs[0].Answer) == 0 {
|
||||
t.Logf("%+v\n", w)
|
||||
t.Fatal("Did not get back an answer")
|
||||
}
|
||||
|
||||
// Ensure first record is a SOA
|
||||
if gotRRs[0].Header().Rrtype != dns.TypeSOA {
|
||||
t.Error("Invalid transfer response, does not start with SOA record")
|
||||
}
|
||||
}
|
||||
|
||||
func validateAXFR(t *testing.T, ch <-chan []dns.RR) {
|
||||
xfr := []dns.RR{}
|
||||
for rrs := range ch {
|
||||
xfr = append(xfr, rrs...)
|
||||
}
|
||||
if xfr[0].Header().Rrtype != dns.TypeSOA {
|
||||
t.Error("Invalid transfer response, does not start with SOA record")
|
||||
// Ensure xfr starts with SOA
|
||||
if w.Msgs[0].Answer[0].Header().Rrtype != dns.TypeSOA {
|
||||
t.Error("Invalid XFR, does not start with SOA record")
|
||||
}
|
||||
|
||||
zp := dns.NewZoneParser(strings.NewReader(expectedZone), "", "")
|
||||
i := 0
|
||||
for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
|
||||
if !dns.IsDuplicate(rr, xfr[i]) {
|
||||
t.Fatalf("Record %d, expected\n%v\n, got\n%v", i, rr, xfr[i])
|
||||
// Ensure xfr starts with SOA
|
||||
// Last message is empty, so we need to go back one further
|
||||
if w.Msgs[len(w.Msgs)-2].Answer[len(w.Msgs[len(w.Msgs)-2].Answer)-1].Header().Rrtype != dns.TypeSOA {
|
||||
t.Error("Invalid XFR, does not end with SOA record")
|
||||
}
|
||||
|
||||
testRRs := []dns.RR{}
|
||||
for _, tc := range dnsTestCases {
|
||||
if tc.Rcode != dns.RcodeSuccess {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ans := range tc.Answer {
|
||||
// Exclude wildcard searches
|
||||
if strings.Contains(ans.Header().Name, "*") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Exclude TXT records
|
||||
if ans.Header().Rrtype == dns.TypeTXT {
|
||||
continue
|
||||
}
|
||||
testRRs = append(testRRs, ans)
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
if err := zp.Err(); err != nil {
|
||||
t.Fatal(err)
|
||||
gotRRs := []dns.RR{}
|
||||
for _, resp := range w.Msgs {
|
||||
for _, ans := range resp.Answer {
|
||||
// Skip SOA records since these
|
||||
// test cases do not exist
|
||||
if ans.Header().Rrtype == dns.TypeSOA {
|
||||
continue
|
||||
}
|
||||
|
||||
gotRRs = append(gotRRs, ans)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
diff := difference(testRRs, gotRRs)
|
||||
if len(diff) != 0 {
|
||||
t.Errorf("Got back %d records that do not exist in test cases, should be 0:", len(diff))
|
||||
for _, rec := range diff {
|
||||
t.Errorf("%+v", rec)
|
||||
}
|
||||
}
|
||||
|
||||
diff = difference(gotRRs, testRRs)
|
||||
if len(diff) != 0 {
|
||||
t.Errorf("Found %d records we're missing, should be 0:", len(diff))
|
||||
for _, rec := range diff {
|
||||
t.Errorf("%+v", rec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const expectedZone = `
|
||||
cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 3 7200 1800 86400 5
|
||||
external.testns.svc.cluster.local. 5 IN CNAME ext.interwebs.test.
|
||||
external-to-service.testns.svc.cluster.local. 5 IN CNAME svc1.testns.svc.cluster.local.
|
||||
hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.2
|
||||
172-0-0-2.hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.2
|
||||
_http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 172-0-0-2.hdls1.testns.svc.cluster.local.
|
||||
hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.3
|
||||
172-0-0-3.hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.3
|
||||
_http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 172-0-0-3.hdls1.testns.svc.cluster.local.
|
||||
hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.4
|
||||
dup-name.hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.4
|
||||
_http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 dup-name.hdls1.testns.svc.cluster.local.
|
||||
hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.5
|
||||
dup-name.hdls1.testns.svc.cluster.local. 5 IN A 172.0.0.5
|
||||
_http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 dup-name.hdls1.testns.svc.cluster.local.
|
||||
hdls1.testns.svc.cluster.local. 5 IN AAAA 5678:abcd::1
|
||||
5678-abcd--1.hdls1.testns.svc.cluster.local. 5 IN AAAA 5678:abcd::1
|
||||
_http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 5678-abcd--1.hdls1.testns.svc.cluster.local.
|
||||
hdls1.testns.svc.cluster.local. 5 IN AAAA 5678:abcd::2
|
||||
5678-abcd--2.hdls1.testns.svc.cluster.local. 5 IN AAAA 5678:abcd::2
|
||||
_http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 5678-abcd--2.hdls1.testns.svc.cluster.local.
|
||||
hdlsprtls.testns.svc.cluster.local. 5 IN A 172.0.0.20
|
||||
172-0-0-20.hdlsprtls.testns.svc.cluster.local. 5 IN A 172.0.0.20
|
||||
svc1.testns.svc.cluster.local. 5 IN A 10.0.0.1
|
||||
svc1.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc1.testns.svc.cluster.local.
|
||||
_http._tcp.svc1.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc1.testns.svc.cluster.local.
|
||||
svc6.testns.svc.cluster.local. 5 IN AAAA 1234:abcd::1
|
||||
svc6.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc6.testns.svc.cluster.local.
|
||||
_http._tcp.svc6.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc6.testns.svc.cluster.local.
|
||||
svcempty.testns.svc.cluster.local. 5 IN A 10.0.0.1
|
||||
svcempty.testns.svc.cluster.local. 5 IN SRV 0 100 80 svcempty.testns.svc.cluster.local.
|
||||
_http._tcp.svcempty.testns.svc.cluster.local. 5 IN SRV 0 100 80 svcempty.testns.svc.cluster.local.
|
||||
cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 3 7200 1800 86400 5
|
||||
`
|
||||
func TestKubernetesXFRNotAllowed(t *testing.T) {
|
||||
k := New([]string{"cluster.local."})
|
||||
k.APIConn = &APIConnServeTest{}
|
||||
k.TransferTo = []string{"1.2.3.4:53"}
|
||||
k.Namespaces = map[string]struct{}{"testns": {}}
|
||||
|
||||
ctx := context.TODO()
|
||||
w := dnstest.NewMultiRecorder(&test.ResponseWriter{})
|
||||
dnsmsg := &dns.Msg{}
|
||||
dnsmsg.SetAxfr(k.Zones[0])
|
||||
|
||||
_, err := k.ServeDNS(ctx, w, dnsmsg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(w.Msgs) == 0 {
|
||||
t.Logf("%+v\n", w)
|
||||
t.Fatal("Did not get back a zone response")
|
||||
}
|
||||
|
||||
if len(w.Msgs[0].Answer) != 0 {
|
||||
t.Logf("%+v\n", w)
|
||||
t.Fatal("Got an answer, should not have")
|
||||
}
|
||||
}
|
||||
|
||||
// difference shows what we're missing when comparing two RR slices
|
||||
func difference(testRRs []dns.RR, gotRRs []dns.RR) []dns.RR {
|
||||
expectedRRs := map[string]struct{}{}
|
||||
for _, rr := range testRRs {
|
||||
expectedRRs[rr.String()] = struct{}{}
|
||||
}
|
||||
|
||||
foundRRs := []dns.RR{}
|
||||
for _, rr := range gotRRs {
|
||||
if _, ok := expectedRRs[rr.String()]; !ok {
|
||||
foundRRs = append(foundRRs, rr)
|
||||
}
|
||||
}
|
||||
return foundRRs
|
||||
}
|
||||
|
||||
func TestEndpointsEquivalent(t *testing.T) {
|
||||
epA := object.Endpoints{
|
||||
Subsets: []object.EndpointSubset{{
|
||||
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
|
||||
}},
|
||||
}
|
||||
epB := object.Endpoints{
|
||||
Subsets: []object.EndpointSubset{{
|
||||
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
|
||||
}},
|
||||
}
|
||||
epC := object.Endpoints{
|
||||
Subsets: []object.EndpointSubset{{
|
||||
Addresses: []object.EndpointAddress{{IP: "1.2.3.5", Hostname: "foo"}},
|
||||
}},
|
||||
}
|
||||
epD := object.Endpoints{
|
||||
Subsets: []object.EndpointSubset{{
|
||||
Addresses: []object.EndpointAddress{{IP: "1.2.3.5", Hostname: "foo"}},
|
||||
},
|
||||
{
|
||||
Addresses: []object.EndpointAddress{{IP: "1.2.2.2", Hostname: "foofoo"}},
|
||||
}},
|
||||
}
|
||||
epE := object.Endpoints{
|
||||
Subsets: []object.EndpointSubset{{
|
||||
Addresses: []object.EndpointAddress{{IP: "1.2.3.5", Hostname: "foo"}, {IP: "1.1.1.1"}},
|
||||
}},
|
||||
}
|
||||
epF := object.Endpoints{
|
||||
Subsets: []object.EndpointSubset{{
|
||||
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foofoo"}},
|
||||
}},
|
||||
}
|
||||
epG := object.Endpoints{
|
||||
Subsets: []object.EndpointSubset{{
|
||||
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
|
||||
Ports: []object.EndpointPort{{Name: "http", Port: 80, Protocol: "TCP"}},
|
||||
}},
|
||||
}
|
||||
epH := object.Endpoints{
|
||||
Subsets: []object.EndpointSubset{{
|
||||
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
|
||||
Ports: []object.EndpointPort{{Name: "newportname", Port: 80, Protocol: "TCP"}},
|
||||
}},
|
||||
}
|
||||
epI := object.Endpoints{
|
||||
Subsets: []object.EndpointSubset{{
|
||||
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
|
||||
Ports: []object.EndpointPort{{Name: "http", Port: 8080, Protocol: "TCP"}},
|
||||
}},
|
||||
}
|
||||
epJ := object.Endpoints{
|
||||
Subsets: []object.EndpointSubset{{
|
||||
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
|
||||
Ports: []object.EndpointPort{{Name: "http", Port: 80, Protocol: "UDP"}},
|
||||
}},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
equiv bool
|
||||
a *object.Endpoints
|
||||
b *object.Endpoints
|
||||
}{
|
||||
{true, &epA, &epB},
|
||||
{false, &epA, &epC},
|
||||
{false, &epA, &epD},
|
||||
{false, &epA, &epE},
|
||||
{false, &epA, &epF},
|
||||
{false, &epF, &epG},
|
||||
{false, &epG, &epH},
|
||||
{false, &epG, &epI},
|
||||
{false, &epG, &epJ},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
if tc.equiv && !endpointsEquivalent(tc.a, tc.b) {
|
||||
t.Errorf("Test %d: expected endpoints to be equivalent and they are not.", i)
|
||||
}
|
||||
if !tc.equiv && endpointsEquivalent(tc.a, tc.b) {
|
||||
t.Errorf("Test %d: expected endpoints to be seen as different but they were not.", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user