k8s/autopath: Add CNAMES (#771)
* Add unit tests & cnames * more progress * fix * next mw dependent unit tests * add tests for OnNXDOMAIN * Add AAAA and ndots unit tests; fix request.NewWithQuestion * Correct default value in README * add CNAMEs to readme * review * fix autopath examples * fix and test CNAME response order
This commit is contained in:
parent
0049230a93
commit
8495e48297
7 changed files with 586 additions and 28 deletions
|
@ -156,7 +156,7 @@ specified).
|
|||
|
||||
If no domains in the path produce an answer, a lookup on the bare question will be attempted.
|
||||
|
||||
A successful response will contain a question section with the original question, and an answer section containing the record for the question that actually had an answer. This means that the question and answer will not match. For example:
|
||||
A successful response will contain a question section with the original question, and an answer section containing the record for the question that actually had an answer. This means that the question and answer will not match. To avoid potential client confusion, a dynamically generated CNAME entry is added to join the two. For example:
|
||||
|
||||
```
|
||||
# host -v -t a google.com
|
||||
|
@ -168,6 +168,7 @@ specified).
|
|||
;google.com.default.svc.cluster.local. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
google.com.default.svc.cluster.local. 175 IN CNAME google.com.
|
||||
google.com. 175 IN A 216.58.194.206
|
||||
```
|
||||
|
||||
|
@ -178,14 +179,16 @@ specified).
|
|||
```
|
||||
# host -t a google.com
|
||||
google.com has address 216.58.194.206
|
||||
google.com.default.svc.cluster.local is an alias for google.com.
|
||||
|
||||
# host -t a google.com.default.svc.cluster.local.
|
||||
google.com has address 216.58.194.206
|
||||
google.com.default.svc.cluster.local is an alias for google.com.
|
||||
```
|
||||
|
||||
**NDOTS** (default: `0`) This provides an adjustable threshold to prevent server side lookups from triggering. If the number of dots before the first search domain is less than this number, then the search path will not executed on the server side. When autopath is enabled with default settings, the search path is always conducted when the query is in the first search domain `<pod-namespace>.svc.<zone>.`.
|
||||
|
||||
**RESPONSE** (default: `SERVFAIL`) This option causes the kubernetes middleware to return the given response instead of NXDOMAIN when the all searches in the path produce no results. Valid values: `NXDOMAIN`, `SERVFAIL` or `NOERROR`. Setting this to `SERVFAIL` or `NOERROR` should prevent the client from fruitlessly continuing the client side searches in the path after the server already checked them.
|
||||
**RESPONSE** (default: `NOERROR`) This option causes the kubernetes middleware to return the given response instead of NXDOMAIN when the all searches in the path produce no results. Valid values: `NXDOMAIN`, `SERVFAIL` or `NOERROR`. Setting this to `SERVFAIL` or `NOERROR` should prevent the client from fruitlessly continuing the client side searches in the path after the server already checked them.
|
||||
|
||||
**RESOLV-CONF** (default: `/etc/resolv.conf`) If specified, the kubernetes middleware uses this file to get the host's search domains. The kubernetes middleware performs a lookup on these domains if the in-cluster search domains in the path fail to produce an answer. If not specified, the values will be read from the local resolv.conf file (i.e the resolv.conf file in the pod containing CoreDNS). In practice, this option should only need to be used if running CoreDNS outside of the cluster and the search path in /etc/resolv.conf does not match the cluster's "default" dns-policiy.
|
||||
|
||||
|
@ -245,7 +248,7 @@ specified).
|
|||
cidrs 10.0.0.0/24
|
||||
pods verified
|
||||
upstream 10.102.3.10:53
|
||||
autopath 0 SERVFAIL cluster.conf
|
||||
autopath 0 NOERROR cluster.conf
|
||||
}
|
||||
|
||||
|
||||
|
|
78
middleware/kubernetes/autopath.go
Normal file
78
middleware/kubernetes/autopath.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package kubernetes
|
||||
|
||||
import "github.com/miekg/dns"
|
||||
|
||||
// AutoPathWriter implements a ResponseWriter that also does the following:
|
||||
// * reverts question section of a packet to its original state.
|
||||
// This is done to avoid the 'Question section mismatch:' error in client.
|
||||
// * Defers write to the client if the response code is NXDOMAIN. This is needed
|
||||
// to enable further search path probing if a search was not sucessful.
|
||||
// * Allow forced write to client regardless of response code. This is needed to
|
||||
// write the packet to the client if the final search in the path fails to
|
||||
// produce results.
|
||||
// * Overwrites response code with AutoPathWriter.Rcode if the response code
|
||||
// is NXDOMAIN (NameError). This is needed to support the AutoPath.OnNXDOMAIN
|
||||
// function, which returns a NOERROR to client instead of NXDOMAIN if the final
|
||||
// search in the path fails to produce results.
|
||||
|
||||
type AutoPathWriter struct {
|
||||
dns.ResponseWriter
|
||||
original dns.Question
|
||||
Rcode int
|
||||
Sent bool
|
||||
}
|
||||
|
||||
// NewAutoPathWriter returns a pointer to a new AutoPathWriter
|
||||
func NewAutoPathWriter(w dns.ResponseWriter, r *dns.Msg) *AutoPathWriter {
|
||||
return &AutoPathWriter{
|
||||
ResponseWriter: w,
|
||||
original: r.Question[0],
|
||||
Rcode: dns.RcodeSuccess,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteMsg writes to client, unless response will be NXDOMAIN
|
||||
func (apw *AutoPathWriter) WriteMsg(res *dns.Msg) error {
|
||||
return apw.overrideMsg(res, false)
|
||||
}
|
||||
|
||||
// ForceWriteMsg forces the write to client regardless of response code
|
||||
func (apw *AutoPathWriter) ForceWriteMsg(res *dns.Msg) error {
|
||||
return apw.overrideMsg(res, true)
|
||||
}
|
||||
|
||||
// overrideMsg overrides rcode, reverts question, adds CNAME, and calls the
|
||||
// underlying ResponseWriter's WriteMsg method unless the write is deferred,
|
||||
// or force = true.
|
||||
func (apw *AutoPathWriter) overrideMsg(res *dns.Msg, force bool) error {
|
||||
if res.Rcode == dns.RcodeNameError {
|
||||
res.Rcode = apw.Rcode
|
||||
}
|
||||
if res.Rcode != dns.RcodeSuccess && !force {
|
||||
return nil
|
||||
}
|
||||
for _, a := range res.Answer {
|
||||
if apw.original.Name == a.Header().Name {
|
||||
continue
|
||||
}
|
||||
res.Answer = append(res.Answer, nil)
|
||||
copy(res.Answer[1:], res.Answer)
|
||||
res.Answer[0] = newCNAME(apw.original.Name, dns.Fqdn(a.Header().Name), a.Header().Ttl)
|
||||
}
|
||||
res.Question[0] = apw.original
|
||||
apw.Sent = true
|
||||
return apw.ResponseWriter.WriteMsg(res)
|
||||
}
|
||||
|
||||
// Write is a wrapper that records the size of the message that gets written.
|
||||
func (apw *AutoPathWriter) Write(buf []byte) (int, error) {
|
||||
n, err := apw.ResponseWriter.Write(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Hijack implements dns.Hijacker. It simply wraps the underlying
|
||||
// ResponseWriter's Hijack method if there is one, or returns an error.
|
||||
func (apw *AutoPathWriter) Hijack() {
|
||||
apw.ResponseWriter.Hijack()
|
||||
return
|
||||
}
|
|
@ -6,7 +6,6 @@ import (
|
|||
|
||||
"github.com/coredns/coredns/middleware"
|
||||
"github.com/coredns/coredns/middleware/pkg/dnsutil"
|
||||
"github.com/coredns/coredns/middleware/rewrite"
|
||||
"github.com/coredns/coredns/request"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
@ -23,7 +22,6 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
|
|||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
|
||||
|
||||
// Check that query matches one of the zones served by this middleware,
|
||||
// otherwise delegate to the next in the pipeline.
|
||||
zone := middleware.Zones(k.Zones).Matches(state.Name())
|
||||
|
@ -40,10 +38,10 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
|
|||
// Set the zone to this specific request.
|
||||
zone = state.Name()
|
||||
}
|
||||
|
||||
records, extra, _, err := k.routeRequest(zone, state)
|
||||
|
||||
if k.AutoPath.Enabled && k.IsNameError(err) {
|
||||
// Check for Autopath search eligibility
|
||||
if k.AutoPath.Enabled && k.IsNameError(err) && (state.QType() == dns.TypeA || state.QType() == dns.TypeAAAA) {
|
||||
p := k.findPodWithIP(state.IP())
|
||||
for p != nil {
|
||||
name, path, ok := splitSearch(zone, state.QName(), p.Namespace)
|
||||
|
@ -53,38 +51,50 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
|
|||
if (dns.CountLabel(name) - 1) < k.AutoPath.NDots {
|
||||
break
|
||||
}
|
||||
// Search "svc.cluster.local" and "cluster.local"
|
||||
origQName := state.QName()
|
||||
// Search "svc.cluster.local." and "cluster.local."
|
||||
for i := 0; i < 2; i++ {
|
||||
path = strings.Join(dns.SplitDomainName(path)[1:], ".")
|
||||
state = state.NewWithQuestion(strings.Join([]string{name, path}, "."), state.QType())
|
||||
records, extra, _, err = k.routeRequest(zone, state)
|
||||
newstate := state.NewWithQuestion(strings.Join([]string{name, path}, "."), state.QType())
|
||||
records, extra, _, err = k.routeRequest(zone, newstate)
|
||||
if !k.IsNameError(err) {
|
||||
records = append(records, nil)
|
||||
copy(records[1:], records)
|
||||
records[0] = newCNAME(origQName, records[0].Header().Name, records[0].Header().Ttl)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !k.IsNameError(err) {
|
||||
break
|
||||
}
|
||||
// Fallthrough with the host search path (if set)
|
||||
wr := rewrite.NewResponseReverter(w, r)
|
||||
// Try host search path (if set) in the next middleware
|
||||
apw := NewAutoPathWriter(w, r)
|
||||
for _, hostsearch := range k.AutoPath.HostSearchPath {
|
||||
r = state.NewWithQuestion(strings.Join([]string{name, hostsearch}, "."), state.QType()).Req
|
||||
rcode, nextErr := middleware.NextOrFailure(k.Name(), k.Next, ctx, wr, r)
|
||||
if rcode == dns.RcodeSuccess {
|
||||
newstate := state.NewWithQuestion(strings.Join([]string{name, hostsearch}, "."), state.QType())
|
||||
rcode, nextErr := middleware.NextOrFailure(k.Name(), k.Next, ctx, apw, newstate.Req)
|
||||
if apw.Sent {
|
||||
return rcode, nextErr
|
||||
}
|
||||
}
|
||||
// Search . in this middleware
|
||||
state = state.NewWithQuestion(strings.Join([]string{name, "."}, ""), state.QType())
|
||||
records, extra, _, err = k.routeRequest(zone, state)
|
||||
newstate := state.NewWithQuestion(strings.Join([]string{name, "."}, ""), state.QType())
|
||||
records, extra, _, err = k.routeRequest(zone, newstate)
|
||||
if !k.IsNameError(err) {
|
||||
records = append(records, nil)
|
||||
copy(records[1:], records)
|
||||
records[0] = newCNAME(origQName, records[0].Header().Name, records[0].Header().Ttl)
|
||||
break
|
||||
}
|
||||
// Search . in the next middleware
|
||||
r = state.Req
|
||||
rcode, nextErr := middleware.NextOrFailure(k.Name(), k.Next, ctx, wr, r)
|
||||
if rcode == dns.RcodeNameError {
|
||||
rcode = k.AutoPath.OnNXDOMAIN
|
||||
apw.Rcode = k.AutoPath.OnNXDOMAIN
|
||||
newstate = state.NewWithQuestion(strings.Join([]string{name, "."}, ""), state.QType())
|
||||
r = newstate.Req
|
||||
rcode, nextErr := middleware.NextOrFailure(k.Name(), k.Next, ctx, apw, r)
|
||||
if !apw.Sent && nextErr == nil {
|
||||
r = dnsutil.Dedup(r)
|
||||
state.SizeAndDo(r)
|
||||
r, _ = state.Scrub(r)
|
||||
apw.ForceWriteMsg(r)
|
||||
}
|
||||
return rcode, nextErr
|
||||
}
|
||||
|
@ -115,6 +125,11 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
|
|||
return dns.RcodeSuccess, nil
|
||||
}
|
||||
|
||||
func newCNAME(name string, target string, ttl uint32) *dns.CNAME {
|
||||
// TODO factor this out and put in dnsutil
|
||||
return &dns.CNAME{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: ttl}, Target: dns.Fqdn(target)}
|
||||
}
|
||||
|
||||
func (k *Kubernetes) routeRequest(zone string, state request.Request) (records []dns.RR, extra []dns.RR, debug []dns.RR, err error) {
|
||||
switch state.Type() {
|
||||
case "A":
|
||||
|
|
462
middleware/kubernetes/handler_test.go
Normal file
462
middleware/kubernetes/handler_test.go
Normal file
|
@ -0,0 +1,462 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/coredns/middleware/pkg/dnsrecorder"
|
||||
"github.com/coredns/coredns/middleware/test"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/context"
|
||||
"k8s.io/client-go/1.5/pkg/api"
|
||||
)
|
||||
|
||||
var dnsTestCases = map[string](*test.Case){
|
||||
"A Service": {
|
||||
Qname: "svc1.testns.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.A("svc1.testns.svc.cluster.local. 0 IN A 10.0.0.1"),
|
||||
},
|
||||
},
|
||||
"A Service (Headless)": {
|
||||
Qname: "hdls1.testns.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.A("hdls1.testns.svc.cluster.local. 0 IN A 172.0.0.2"),
|
||||
test.A("hdls1.testns.svc.cluster.local. 0 IN A 172.0.0.3"),
|
||||
},
|
||||
},
|
||||
"SRV Service": {
|
||||
Qname: "_http._tcp.svc1.testns.svc.cluster.local.", Qtype: dns.TypeSRV,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.SRV("_http._tcp.svc1.testns.svc.cluster.local. 0 IN SRV 0 100 80 svc1.testns.svc.cluster.local."),
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
test.A("svc1.testns.svc.cluster.local. 0 IN A 10.0.0.1"),
|
||||
},
|
||||
},
|
||||
"SRV Service (Headless)": {
|
||||
Qname: "_http._tcp.hdls1.testns.svc.cluster.local.", Qtype: dns.TypeSRV,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.SRV("_http._tcp.hdls1.testns.svc.cluster.local. 0 IN SRV 0 50 80 172-0-0-2.hdls1.testns.svc.cluster.local."),
|
||||
test.SRV("_http._tcp.hdls1.testns.svc.cluster.local. 0 IN SRV 0 50 80 172-0-0-3.hdls1.testns.svc.cluster.local."),
|
||||
},
|
||||
Extra: []dns.RR{
|
||||
test.A("172-0-0-2.hdls1.testns.svc.cluster.local. 0 IN A 172.0.0.2"),
|
||||
test.A("172-0-0-3.hdls1.testns.svc.cluster.local. 0 IN A 172.0.0.3"),
|
||||
},
|
||||
},
|
||||
// TODO A External
|
||||
"CNAME External": {
|
||||
Qname: "external.testns.svc.cluster.local.", Qtype: dns.TypeCNAME,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.CNAME("external.testns.svc.cluster.local. 0 IN CNAME ext.interwebs.test."),
|
||||
},
|
||||
},
|
||||
"A Service (Local Federated)": {
|
||||
Qname: "svc1.testns.fed.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.A("svc1.testns.fed.svc.cluster.local. 0 IN A 10.0.0.1"),
|
||||
},
|
||||
},
|
||||
"PTR Service": {
|
||||
Qname: "1.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.PTR("1.0.0.10.in-addr.arpa. 0 IN PTR svc1.testns.svc.cluster.local."),
|
||||
},
|
||||
},
|
||||
// TODO A Service (Remote Federated)
|
||||
"CNAME Service (Remote Federated)": {
|
||||
Qname: "svc0.testns.fed.svc.cluster.local.", Qtype: dns.TypeCNAME,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.CNAME("svc0.testns.fed.svc.cluster.local. 0 IN CNAME svc0.testns.fed.svc.fd-az.fd-r.federal.test."),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var autopathCases = map[string](*test.Case){
|
||||
"A Autopath Service (Second Search)": {
|
||||
Qname: "svc1.testns.podns.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.CNAME("svc1.testns.podns.svc.cluster.local. 0 IN CNAME svc1.testns.svc.cluster.local."),
|
||||
test.A("svc1.testns.svc.cluster.local. 0 IN A 10.0.0.1"),
|
||||
},
|
||||
},
|
||||
"A Autopath Service (Third Search)": {
|
||||
Qname: "svc1.testns.svc.podns.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.CNAME("svc1.testns.svc.podns.svc.cluster.local. 0 IN CNAME svc1.testns.svc.cluster.local."),
|
||||
test.A("svc1.testns.svc.cluster.local. 0 IN A 10.0.0.1"),
|
||||
},
|
||||
},
|
||||
"A Autopath Next Middleware (Host Domain Search)": {
|
||||
Qname: "test1.podns.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.CNAME("test1.podns.svc.cluster.local. 0 IN CNAME test1.hostdom.test."),
|
||||
test.A("test1.hostdom.test. 0 IN A 11.22.33.44"),
|
||||
},
|
||||
},
|
||||
"A Autopath Service (Bare Search)": {
|
||||
Qname: "svc1.testns.svc.cluster.local.podns.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.CNAME("svc1.testns.svc.cluster.local.podns.svc.cluster.local. 0 IN CNAME svc1.testns.svc.cluster.local."),
|
||||
test.A("svc1.testns.svc.cluster.local. 0 IN A 10.0.0.1"),
|
||||
},
|
||||
},
|
||||
"A Autopath Next Middleware (Bare Search)": {
|
||||
Qname: "test2.interwebs.podns.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.CNAME("test2.interwebs.podns.svc.cluster.local. 0 IN CNAME test2.interwebs."),
|
||||
test.A("test2.interwebs. 0 IN A 55.66.77.88"),
|
||||
},
|
||||
},
|
||||
"AAAA Autopath Next Middleware (Bare Search)": {
|
||||
Qname: "test2.interwebs.podns.svc.cluster.local.", Qtype: dns.TypeAAAA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.CNAME("test2.interwebs.podns.svc.cluster.local. 0 IN CNAME test2.interwebs."),
|
||||
test.AAAA("test2.interwebs. 0 IN AAAA 5555:6666:7777::8888"),
|
||||
},
|
||||
},
|
||||
}
|
||||
var autopathBareSearch = map[string](*test.Case){
|
||||
"A Autopath Next Middleware (Bare Search) Non-existing OnNXDOMAIN default": {
|
||||
Qname: "nothere.interwebs.podns.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{},
|
||||
},
|
||||
}
|
||||
var autopathBareSearchExpectNameErr = map[string](*test.Case){
|
||||
"A Autopath Next Middleware (Bare Search) Non-existing OnNXDOMAIN disabled": {
|
||||
Qname: "nothere.interwebs.podns.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeNameError,
|
||||
Answer: []dns.RR{},
|
||||
},
|
||||
}
|
||||
var autopath2NDotsCases = map[string](*test.Case){
|
||||
"A Service (0 Dots)": {
|
||||
Qname: "foo.podns.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeNameError,
|
||||
Answer: []dns.RR{},
|
||||
Ns: []dns.RR{
|
||||
test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"),
|
||||
},
|
||||
},
|
||||
"A Service (1 Dots)": {
|
||||
Qname: "foo.foo.podns.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeNameError,
|
||||
Answer: []dns.RR{},
|
||||
Ns: []dns.RR{
|
||||
test.SOA("cluster.local. 300 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 60"),
|
||||
},
|
||||
},
|
||||
"A Service (2 Dots)": {
|
||||
Qname: "foo.foo.foo.podns.svc.cluster.local.", Qtype: dns.TypeA,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
Answer: []dns.RR{
|
||||
test.A("foo.foo.foo.hostdom.test. 0 IN A 11.22.33.44"),
|
||||
test.CNAME("foo.foo.foo.podns.svc.cluster.local. 0 IN CNAME foo.foo.foo.hostdom.test."),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestServeDNS(t *testing.T) {
|
||||
|
||||
k := Kubernetes{Zones: []string{"cluster.local."}}
|
||||
_, cidr, _ := net.ParseCIDR("10.0.0.0/8")
|
||||
|
||||
k.ReverseCidrs = []net.IPNet{*cidr}
|
||||
k.Federations = []Federation{{name: "fed", zone: "federal.test."}}
|
||||
k.APIConn = &APIConnServeTest{}
|
||||
k.AutoPath.Enabled = true
|
||||
k.AutoPath.HostSearchPath = []string{"hostdom.test"}
|
||||
//k.Proxy = test.MockHandler(nextMWMap)
|
||||
k.Next = testHandler(nextMWMap)
|
||||
|
||||
ctx := context.TODO()
|
||||
runServeDNSTests(t, dnsTestCases, k, ctx)
|
||||
runServeDNSTests(t, autopathCases, k, ctx)
|
||||
runServeDNSTests(t, autopathBareSearch, k, ctx)
|
||||
|
||||
// Set ndots to 2 for the ndots test cases
|
||||
k.AutoPath.NDots = 2
|
||||
runServeDNSTests(t, autopath2NDotsCases, k, ctx)
|
||||
k.AutoPath.NDots = defautNdots
|
||||
|
||||
// Disable the NXDOMAIN override (enabled by default)
|
||||
k.OnNXDOMAIN = dns.RcodeNameError
|
||||
runServeDNSTests(t, autopathCases, k, ctx)
|
||||
runServeDNSTests(t, autopathBareSearchExpectNameErr, k, ctx)
|
||||
|
||||
}
|
||||
|
||||
func runServeDNSTests(t *testing.T, dnsTestCases map[string](*test.Case), k Kubernetes, ctx context.Context) {
|
||||
for testname, tc := range dnsTestCases {
|
||||
testname = "\nTest Case \"" + testname + "\""
|
||||
r := tc.Msg()
|
||||
|
||||
w := dnsrecorder.New(&test.ResponseWriter{})
|
||||
|
||||
_, err := k.ServeDNS(ctx, w, r)
|
||||
if err != nil {
|
||||
t.Errorf("%v expected no error, got %v\n", testname, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := w.Msg
|
||||
|
||||
// Before sorting, make sure that CNAMES do not appear after their target records
|
||||
for i, c := range resp.Answer {
|
||||
if c.Header().Rrtype != dns.TypeCNAME {
|
||||
continue
|
||||
}
|
||||
for _, a := range resp.Answer[:i] {
|
||||
if a.Header().Name != c.(*dns.CNAME).Target {
|
||||
continue
|
||||
}
|
||||
t.Errorf("%v: CNAME found after target record\n", testname)
|
||||
t.Logf("%v Received:\n %v\n", testname, resp)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(test.RRSet(resp.Answer))
|
||||
sort.Sort(test.RRSet(resp.Ns))
|
||||
sort.Sort(test.RRSet(resp.Extra))
|
||||
sort.Sort(test.RRSet(tc.Answer))
|
||||
sort.Sort(test.RRSet(tc.Ns))
|
||||
sort.Sort(test.RRSet(tc.Extra))
|
||||
|
||||
if !test.Header(t, *tc, resp) {
|
||||
t.Logf("%v Received:\n %v\n", testname, resp)
|
||||
continue
|
||||
}
|
||||
if !test.Section(t, *tc, test.Answer, resp.Answer) {
|
||||
t.Logf("%v Received:\n %v\n", testname, resp)
|
||||
}
|
||||
if !test.Section(t, *tc, test.Ns, resp.Ns) {
|
||||
t.Logf("%v Received:\n %v\n", testname, resp)
|
||||
}
|
||||
if !test.Section(t, *tc, test.Extra, resp.Extra) {
|
||||
t.Logf("%v Received:\n %v\n", testname, resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// next middleware question->answer map
|
||||
|
||||
var nextMWMap = map[dns.Question]dns.Msg{
|
||||
{Name: "test1.hostdom.test.", Qtype: dns.TypeA, Qclass: dns.ClassINET}: {
|
||||
Answer: []dns.RR{test.A("test1.hostdom.test. 0 IN A 11.22.33.44")},
|
||||
},
|
||||
{Name: "test2.interwebs.", Qtype: dns.TypeA, Qclass: dns.ClassINET}: {
|
||||
Answer: []dns.RR{test.A("test2.interwebs. 0 IN A 55.66.77.88")},
|
||||
},
|
||||
{Name: "test2.interwebs.", Qtype: dns.TypeAAAA, Qclass: dns.ClassINET}: {
|
||||
Answer: []dns.RR{test.AAAA("test2.interwebs. 0 IN AAAA 5555:6666:7777::8888")},
|
||||
},
|
||||
{Name: "foo.hostdom.test.", Qtype: dns.TypeA, Qclass: dns.ClassINET}: {
|
||||
Answer: []dns.RR{test.A("foo.hostdom.test. 0 IN A 11.22.33.44")},
|
||||
},
|
||||
{Name: "foo.foo.hostdom.test.", Qtype: dns.TypeA, Qclass: dns.ClassINET}: {
|
||||
Answer: []dns.RR{test.A("foo.foo.hostdom.test. 0 IN A 11.22.33.44")},
|
||||
},
|
||||
{Name: "foo.foo.foo.hostdom.test.", Qtype: dns.TypeA, Qclass: dns.ClassINET}: {
|
||||
Answer: []dns.RR{test.A("foo.foo.foo.hostdom.test. 0 IN A 11.22.33.44")},
|
||||
},
|
||||
}
|
||||
|
||||
// testHandler returns a Handler that returns an answer for the question in the
|
||||
// request per the question->answer map qMap.
|
||||
func testHandler(qMap map[dns.Question]dns.Msg) test.Handler {
|
||||
return test.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
msg, ok := qMap[r.Question[0]]
|
||||
if !ok {
|
||||
r.Rcode = dns.RcodeNameError
|
||||
return dns.RcodeNameError, nil
|
||||
}
|
||||
r.Rcode = dns.RcodeSuccess
|
||||
m.Answer = append(m.Answer, msg.Answer...)
|
||||
m.Extra = append(m.Extra, msg.Extra...)
|
||||
w.WriteMsg(m)
|
||||
return dns.RcodeSuccess, nil
|
||||
})
|
||||
}
|
||||
|
||||
type APIConnServeTest struct{}
|
||||
|
||||
func (APIConnServeTest) Run() { return }
|
||||
func (APIConnServeTest) Stop() error { return nil }
|
||||
|
||||
func (APIConnServeTest) PodIndex(string) []interface{} {
|
||||
a := make([]interface{}, 1)
|
||||
a[0] = &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Namespace: "podns",
|
||||
},
|
||||
Status: api.PodStatus{
|
||||
PodIP: "10.240.0.1", // Remote IP set in test.ResponseWriter
|
||||
},
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (APIConnServeTest) ServiceList() []*api.Service {
|
||||
svcs := []*api.Service{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "svc1",
|
||||
Namespace: "testns",
|
||||
},
|
||||
Spec: api.ServiceSpec{
|
||||
ClusterIP: "10.0.0.1",
|
||||
Ports: []api.ServicePort{{
|
||||
Name: "http",
|
||||
Protocol: "tcp",
|
||||
Port: 80,
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "hdls1",
|
||||
Namespace: "testns",
|
||||
},
|
||||
Spec: api.ServiceSpec{
|
||||
ClusterIP: api.ClusterIPNone,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "external",
|
||||
Namespace: "testns",
|
||||
},
|
||||
Spec: api.ServiceSpec{
|
||||
ExternalName: "ext.interwebs.test",
|
||||
Ports: []api.ServicePort{{
|
||||
Name: "http",
|
||||
Protocol: "tcp",
|
||||
Port: 80,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
return svcs
|
||||
|
||||
}
|
||||
|
||||
func (APIConnServeTest) EndpointsList() api.EndpointsList {
|
||||
n := "test.node.foo.bar"
|
||||
|
||||
return api.EndpointsList{
|
||||
Items: []api.Endpoints{
|
||||
{
|
||||
Subsets: []api.EndpointSubset{
|
||||
{
|
||||
Addresses: []api.EndpointAddress{
|
||||
{
|
||||
IP: "172.0.0.1",
|
||||
Hostname: "ep1a",
|
||||
},
|
||||
},
|
||||
Ports: []api.EndpointPort{
|
||||
{
|
||||
Port: 80,
|
||||
Protocol: "tcp",
|
||||
Name: "http",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "svc1",
|
||||
Namespace: "testns",
|
||||
},
|
||||
},
|
||||
{
|
||||
Subsets: []api.EndpointSubset{
|
||||
{
|
||||
Addresses: []api.EndpointAddress{
|
||||
{
|
||||
IP: "172.0.0.2",
|
||||
},
|
||||
},
|
||||
Ports: []api.EndpointPort{
|
||||
{
|
||||
Port: 80,
|
||||
Protocol: "tcp",
|
||||
Name: "http",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "hdls1",
|
||||
Namespace: "testns",
|
||||
},
|
||||
},
|
||||
{
|
||||
Subsets: []api.EndpointSubset{
|
||||
{
|
||||
Addresses: []api.EndpointAddress{
|
||||
{
|
||||
IP: "172.0.0.3",
|
||||
},
|
||||
},
|
||||
Ports: []api.EndpointPort{
|
||||
{
|
||||
Port: 80,
|
||||
Protocol: "tcp",
|
||||
Name: "http",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "hdls1",
|
||||
Namespace: "testns",
|
||||
},
|
||||
},
|
||||
{
|
||||
Subsets: []api.EndpointSubset{
|
||||
{
|
||||
Addresses: []api.EndpointAddress{
|
||||
{
|
||||
IP: "10.9.8.7",
|
||||
NodeName: &n,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (APIConnServeTest) GetNodeByName(name string) (api.Node, error) {
|
||||
return api.Node{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "test.node.foo.bar",
|
||||
Labels: map[string]string{
|
||||
labelRegion: "fd-r",
|
||||
labelAvailabilityZone: "fd-az",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
|
@ -110,13 +110,14 @@ var errResolvConfReadErr = errors.New("resolv.conf read error")
|
|||
|
||||
// Services implements the ServiceBackend interface.
|
||||
func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) (svcs []msg.Service, debug []msg.Service, err error) {
|
||||
|
||||
r, e := k.parseRequest(state.Name(), state.QType())
|
||||
if e != nil {
|
||||
return nil, nil, e
|
||||
}
|
||||
|
||||
switch state.Type() {
|
||||
case "AAAA":
|
||||
// AAAA not implemented
|
||||
return nil, nil, errNoItems
|
||||
case "A", "CNAME":
|
||||
if state.Type() == "A" && isDefaultNS(state.Name(), r) {
|
||||
// If this is an A request for "ns.dns", respond with a "fake" record for coredns.
|
||||
|
@ -362,7 +363,6 @@ func (k *Kubernetes) Entries(r recordRequest) ([]msg.Service, error) {
|
|||
if (!symbolContainsWildcard(r.namespace)) && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(r.namespace, k.Namespaces)) {
|
||||
return nil, errNsNotExposed
|
||||
}
|
||||
|
||||
services, pods, err := k.get(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -464,7 +464,7 @@ func ipFromPodName(podname string) string {
|
|||
}
|
||||
|
||||
func (k *Kubernetes) findPodWithIP(ip string) (p *api.Pod) {
|
||||
if k.PodMode != PodModeVerified {
|
||||
if !k.AutoPath.Enabled {
|
||||
return nil
|
||||
}
|
||||
objList := k.APIConn.PodIndex(ip)
|
||||
|
@ -533,7 +533,6 @@ func (k *Kubernetes) get(r recordRequest) (services []service, pods []pod, err e
|
|||
|
||||
func (k *Kubernetes) findServices(r recordRequest) ([]service, error) {
|
||||
serviceList := k.APIConn.ServiceList()
|
||||
|
||||
var resultItems []service
|
||||
|
||||
nsWildcard := symbolContainsWildcard(r.namespace)
|
||||
|
|
|
@ -59,6 +59,7 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
|
|||
ResyncPeriod: defaultResyncPeriod,
|
||||
interfaceAddrs: &interfaceAddrs{},
|
||||
PodMode: PodModeDisabled,
|
||||
Proxy: proxy.Proxy{},
|
||||
}
|
||||
|
||||
for c.Next() {
|
||||
|
@ -244,5 +245,5 @@ const (
|
|||
defaultPodMode = PodModeDisabled
|
||||
defautNdots = 0
|
||||
defaultResolvConfFile = "/etc/resolv.conf"
|
||||
defaultOnNXDOMAIN = dns.RcodeServerFailure
|
||||
defaultOnNXDOMAIN = dns.RcodeSuccess
|
||||
)
|
||||
|
|
|
@ -29,7 +29,7 @@ type Request struct {
|
|||
// section in the request.
|
||||
func (r *Request) NewWithQuestion(name string, typ uint16) Request {
|
||||
req1 := Request{W: r.W, Req: r.Req.Copy()}
|
||||
req1.Req.Question[0] = dns.Question{Name: dns.Fqdn(name), Qtype: dns.ClassINET, Qclass: typ}
|
||||
req1.Req.Question[0] = dns.Question{Name: dns.Fqdn(name), Qclass: dns.ClassINET, Qtype: typ}
|
||||
return req1
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue