Commit e80d6965 authored by Chris O'Haver's avatar Chris O'Haver Committed by GitHub

plugin/k8s_external: Add support for PTR requests (#5435)

* Exclude External IP addresses from being added to the existing kubernetes' plugin IP->Service index
* Add support for PTR requests on External IPs of Services to the k8s_external plugin
Signed-off-by: default avatarChris O'Haver <cohaver@infoblox.com>
parent d903a963
...@@ -10,7 +10,7 @@ This plugin allows an additional zone to resolve the external IP address(es) of ...@@ -10,7 +10,7 @@ This plugin allows an additional zone to resolve the external IP address(es) of
service. This plugin is only useful if the *kubernetes* plugin is also loaded. service. This plugin is only useful if the *kubernetes* plugin is also loaded.
The plugin uses an external zone to resolve in-cluster IP addresses. It only handles queries for A, The plugin uses an external zone to resolve in-cluster IP addresses. It only handles queries for A,
AAAA and SRV records; all others result in NODATA responses. To make it a proper DNS zone, it handles AAAA, SRV, and PTR records; all others result in NODATA responses. To make it a proper DNS zone, it handles
SOA and NS queries for the apex of the zone. SOA and NS queries for the apex of the zone.
By default the apex of the zone will look like the following (assuming the zone used is `example.org`): By default the apex of the zone will look like the following (assuming the zone used is `example.org`):
...@@ -101,6 +101,3 @@ zone transfers. Notifies are not supported. ...@@ -101,6 +101,3 @@ zone transfers. Notifies are not supported.
For some background see [resolve external IP address](https://github.com/kubernetes/dns/issues/242). For some background see [resolve external IP address](https://github.com/kubernetes/dns/issues/242).
And [A records for services with Load Balancer IP](https://github.com/coredns/coredns/issues/1851). And [A records for services with Load Balancer IP](https://github.com/coredns/coredns/issues/1851).
# Bugs
PTR queries for the reverse zone is not supported.
...@@ -105,6 +105,8 @@ func (e *External) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms ...@@ -105,6 +105,8 @@ func (e *External) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
m.Answer, m.Truncated = e.aaaa(ctx, svc, state) m.Answer, m.Truncated = e.aaaa(ctx, svc, state)
case dns.TypeSRV: case dns.TypeSRV:
m.Answer, m.Extra = e.srv(ctx, svc, state) m.Answer, m.Extra = e.srv(ctx, svc, state)
case dns.TypePTR:
m.Answer = e.ptr(svc, state)
default: default:
m.Ns = []dns.RR{e.soa(state)} m.Ns = []dns.RR{e.soa(state)}
} }
......
...@@ -20,7 +20,7 @@ func TestExternal(t *testing.T) { ...@@ -20,7 +20,7 @@ func TestExternal(t *testing.T) {
k.APIConn = &external{} k.APIConn = &external{}
e := New() e := New()
e.Zones = []string{"example.com."} e.Zones = []string{"example.com.", "in-addr.arpa."}
e.Next = test.NextHandler(dns.RcodeSuccess, nil) e.Next = test.NextHandler(dns.RcodeSuccess, nil)
e.externalFunc = k.External e.externalFunc = k.External
e.externalAddrFunc = externalAddress // internal test function e.externalAddrFunc = externalAddress // internal test function
...@@ -49,12 +49,33 @@ func TestExternal(t *testing.T) { ...@@ -49,12 +49,33 @@ func TestExternal(t *testing.T) {
t.Error("Expected authoritative answer") t.Error("Expected authoritative answer")
} }
if err = test.SortAndCheck(resp, tc); err != nil { if err = test.SortAndCheck(resp, tc); err != nil {
t.Error(err) t.Errorf("Test %d: %v", i, err)
} }
} }
} }
var tests = []test.Case{ var tests = []test.Case{
// PTR reverse lookup
{
Qname: "4.3.2.1.in-addr.arpa.", Qtype: dns.TypePTR, Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.PTR("4.3.2.1.in-addr.arpa. 5 IN PTR svc1.testns.example.com."),
},
},
// Bad PTR reverse lookup using existing service name
{
Qname: "svc1.testns.example.com.", Qtype: dns.TypePTR, Rcode: dns.RcodeSuccess,
Ns: []dns.RR{
test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"),
},
},
// Bad PTR reverse lookup using non-existing service name
{
Qname: "not-existing.testns.example.com.", Qtype: dns.TypePTR, Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("example.com. 5 IN SOA ns1.dns.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"),
},
},
// A Service // A Service
{ {
Qname: "svc1.testns.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess, Qname: "svc1.testns.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess,
...@@ -155,7 +176,7 @@ var tests = []test.Case{ ...@@ -155,7 +176,7 @@ var tests = []test.Case{
{ {
Qname: "svc11.testns.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess, Qname: "svc11.testns.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess,
Answer: []dns.RR{ Answer: []dns.RR{
test.A("svc11.testns.example.com. 5 IN A 1.2.3.4"), test.A("svc11.testns.example.com. 5 IN A 2.3.4.5"),
}, },
}, },
{ {
...@@ -164,7 +185,7 @@ var tests = []test.Case{ ...@@ -164,7 +185,7 @@ var tests = []test.Case{
test.SRV("_http._tcp.svc11.testns.example.com. 5 IN SRV 0 100 80 svc11.testns.example.com."), test.SRV("_http._tcp.svc11.testns.example.com. 5 IN SRV 0 100 80 svc11.testns.example.com."),
}, },
Extra: []dns.RR{ Extra: []dns.RR{
test.A("svc11.testns.example.com. 5 IN A 1.2.3.4"), test.A("svc11.testns.example.com. 5 IN A 2.3.4.5"),
}, },
}, },
{ {
...@@ -173,7 +194,7 @@ var tests = []test.Case{ ...@@ -173,7 +194,7 @@ var tests = []test.Case{
test.SRV("svc11.testns.example.com. 5 IN SRV 0 100 80 svc11.testns.example.com."), test.SRV("svc11.testns.example.com. 5 IN SRV 0 100 80 svc11.testns.example.com."),
}, },
Extra: []dns.RR{ Extra: []dns.RR{
test.A("svc11.testns.example.com. 5 IN A 1.2.3.4"), test.A("svc11.testns.example.com. 5 IN A 2.3.4.5"),
}, },
}, },
// svc12 // svc12
...@@ -211,6 +232,20 @@ func (external) GetNodeByName(ctx context.Context, name string) (*api.Node, erro ...@@ -211,6 +232,20 @@ func (external) GetNodeByName(ctx context.Context, name string) (*api.Node, erro
func (external) SvcIndex(s string) []*object.Service { return svcIndexExternal[s] } func (external) SvcIndex(s string) []*object.Service { return svcIndexExternal[s] }
func (external) PodIndex(string) []*object.Pod { return nil } func (external) PodIndex(string) []*object.Pod { return nil }
func (external) SvcExtIndexReverse(ip string) (result []*object.Service) {
for _, svcs := range svcIndexExternal {
for _, svc := range svcs {
for _, exIp := range svc.ExternalIPs {
if exIp != ip {
continue
}
result = append(result, svc)
}
}
}
return result
}
func (external) GetNamespaceByName(name string) (*object.Namespace, error) { func (external) GetNamespaceByName(name string) (*object.Namespace, error) {
return &object.Namespace{ return &object.Namespace{
Name: name, Name: name,
...@@ -243,7 +278,7 @@ var svcIndexExternal = map[string][]*object.Service{ ...@@ -243,7 +278,7 @@ var svcIndexExternal = map[string][]*object.Service{
Name: "svc11", Name: "svc11",
Namespace: "testns", Namespace: "testns",
Type: api.ServiceTypeLoadBalancer, Type: api.ServiceTypeLoadBalancer,
ExternalIPs: []string{"1.2.3.4"}, ExternalIPs: []string{"2.3.4.5"},
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
}, },
}, },
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"math" "math"
"github.com/coredns/coredns/plugin/etcd/msg" "github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/request" "github.com/coredns/coredns/request"
"github.com/miekg/dns" "github.com/miekg/dns"
...@@ -76,6 +77,19 @@ func (e *External) aaaa(ctx context.Context, services []msg.Service, state reque ...@@ -76,6 +77,19 @@ func (e *External) aaaa(ctx context.Context, services []msg.Service, state reque
return records, truncated return records, truncated
} }
func (e *External) ptr(services []msg.Service, state request.Request) (records []dns.RR) {
dup := make(map[string]struct{})
for _, s := range services {
if _, ok := dup[s.Host]; !ok {
dup[s.Host] = struct{}{}
rr := s.NewPTR(state.QName(), dnsutil.Join(s.Host, e.Zones[0]))
rr.Hdr.Ttl = e.ttl
records = append(records, rr)
}
}
return records
}
func (e *External) srv(ctx context.Context, services []msg.Service, state request.Request) (records, extra []dns.RR) { func (e *External) srv(ctx context.Context, services []msg.Service, state request.Request) (records, extra []dns.RR) {
dup := make(map[item]struct{}) dup := make(map[item]struct{})
......
...@@ -59,6 +59,11 @@ func TestTransferAXFR(t *testing.T) { ...@@ -59,6 +59,11 @@ func TestTransferAXFR(t *testing.T) {
if ans.Header().Rrtype == dns.TypeTXT { if ans.Header().Rrtype == dns.TypeTXT {
continue continue
} }
// Exclude PTR records
if ans.Header().Rrtype == dns.TypePTR {
continue
}
expect = append(expect, ans) expect = append(expect, ans)
} }
} }
......
...@@ -25,6 +25,7 @@ const ( ...@@ -25,6 +25,7 @@ const (
podIPIndex = "PodIP" podIPIndex = "PodIP"
svcNameNamespaceIndex = "ServiceNameNamespace" svcNameNamespaceIndex = "ServiceNameNamespace"
svcIPIndex = "ServiceIP" svcIPIndex = "ServiceIP"
svcExtIPIndex = "ServiceExternalIP"
epNameNamespaceIndex = "EndpointNameNamespace" epNameNamespaceIndex = "EndpointNameNamespace"
epIPIndex = "EndpointsIP" epIPIndex = "EndpointsIP"
) )
...@@ -34,6 +35,7 @@ type dnsController interface { ...@@ -34,6 +35,7 @@ type dnsController interface {
EndpointsList() []*object.Endpoints EndpointsList() []*object.Endpoints
SvcIndex(string) []*object.Service SvcIndex(string) []*object.Service
SvcIndexReverse(string) []*object.Service SvcIndexReverse(string) []*object.Service
SvcExtIndexReverse(string) []*object.Service
PodIndex(string) []*object.Pod PodIndex(string) []*object.Pod
EpIndex(string) []*object.Endpoints EpIndex(string) []*object.Endpoints
EpIndexReverse(string) []*object.Endpoints EpIndexReverse(string) []*object.Endpoints
...@@ -122,7 +124,7 @@ func newdnsController(ctx context.Context, kubeClient kubernetes.Interface, opts ...@@ -122,7 +124,7 @@ func newdnsController(ctx context.Context, kubeClient kubernetes.Interface, opts
}, },
&api.Service{}, &api.Service{},
cache.ResourceEventHandlerFuncs{AddFunc: dns.Add, UpdateFunc: dns.Update, DeleteFunc: dns.Delete}, cache.ResourceEventHandlerFuncs{AddFunc: dns.Add, UpdateFunc: dns.Update, DeleteFunc: dns.Delete},
cache.Indexers{svcNameNamespaceIndex: svcNameNamespaceIndexFunc, svcIPIndex: svcIPIndexFunc}, cache.Indexers{svcNameNamespaceIndex: svcNameNamespaceIndexFunc, svcIPIndex: svcIPIndexFunc, svcExtIPIndex: svcExtIPIndexFunc},
object.DefaultProcessor(object.ToService, nil), object.DefaultProcessor(object.ToService, nil),
) )
...@@ -232,12 +234,18 @@ func svcIPIndexFunc(obj interface{}) ([]string, error) { ...@@ -232,12 +234,18 @@ func svcIPIndexFunc(obj interface{}) ([]string, error) {
if !ok { if !ok {
return nil, errObj return nil, errObj
} }
idx := make([]string, len(svc.ClusterIPs)+len(svc.ExternalIPs)) idx := make([]string, len(svc.ClusterIPs))
copy(idx, svc.ClusterIPs) copy(idx, svc.ClusterIPs)
if len(svc.ExternalIPs) == 0 { return idx, nil
return idx, nil }
func svcExtIPIndexFunc(obj interface{}) ([]string, error) {
svc, ok := obj.(*object.Service)
if !ok {
return nil, errObj
} }
copy(idx[len(svc.ClusterIPs):], svc.ExternalIPs) idx := make([]string, len(svc.ExternalIPs))
copy(idx, svc.ExternalIPs)
return idx, nil return idx, nil
} }
...@@ -502,6 +510,22 @@ func (dns *dnsControl) SvcIndexReverse(ip string) (svcs []*object.Service) { ...@@ -502,6 +510,22 @@ func (dns *dnsControl) SvcIndexReverse(ip string) (svcs []*object.Service) {
return svcs return svcs
} }
func (dns *dnsControl) SvcExtIndexReverse(ip string) (svcs []*object.Service) {
os, err := dns.svcLister.ByIndex(svcExtIPIndex, ip)
if err != nil {
return nil
}
for _, o := range os {
s, ok := o.(*object.Service)
if !ok {
continue
}
svcs = append(svcs, s)
}
return svcs
}
func (dns *dnsControl) EpIndex(idx string) (ep []*object.Endpoints) { func (dns *dnsControl) EpIndex(idx string) (ep []*object.Endpoints) {
dns.epLock.RLock() dns.epLock.RLock()
defer dns.epLock.RUnlock() defer dns.epLock.RUnlock()
......
...@@ -14,6 +14,18 @@ import ( ...@@ -14,6 +14,18 @@ import (
// External implements the ExternalFunc call from the external plugin. // External implements the ExternalFunc call from the external plugin.
// It returns any services matching in the services' ExternalIPs. // It returns any services matching in the services' ExternalIPs.
func (k *Kubernetes) External(state request.Request) ([]msg.Service, int) { func (k *Kubernetes) External(state request.Request) ([]msg.Service, int) {
if state.QType() == dns.TypePTR {
ip := dnsutil.ExtractAddressFromReverse(state.Name())
if ip != "" {
svcs, err := k.ExternalReverse(ip)
if err != nil {
return nil, dns.RcodeNameError
}
return svcs, dns.RcodeSuccess
}
// for invalid reverse names, fall through to determine proper nxdomain/nodata response
}
base, _ := dnsutil.TrimZone(state.Name(), state.Zone) base, _ := dnsutil.TrimZone(state.Name(), state.Zone)
segs := dns.SplitDomainName(base) segs := dns.SplitDomainName(base)
...@@ -76,6 +88,10 @@ func (k *Kubernetes) External(state request.Request) ([]msg.Service, int) { ...@@ -76,6 +88,10 @@ func (k *Kubernetes) External(state request.Request) ([]msg.Service, int) {
} }
} }
} }
if state.QType() == dns.TypePTR {
// if this was a PTR request, return empty service list, but retain rcode for proper nxdomain/nodata response
return nil, rcode
}
return services, rcode return services, rcode
} }
...@@ -111,3 +127,24 @@ func (k *Kubernetes) ExternalServices(zone string) (services []msg.Service) { ...@@ -111,3 +127,24 @@ func (k *Kubernetes) ExternalServices(zone string) (services []msg.Service) {
func (k *Kubernetes) ExternalSerial(string) uint32 { func (k *Kubernetes) ExternalSerial(string) uint32 {
return uint32(k.APIConn.Modified(true)) return uint32(k.APIConn.Modified(true))
} }
// ExternalReverse does a reverse lookup for the external IPs
func (k *Kubernetes) ExternalReverse(ip string) ([]msg.Service, error) {
records := k.serviceRecordForExternalIP(ip)
if len(records) == 0 {
return records, errNoItems
}
return records, nil
}
func (k *Kubernetes) serviceRecordForExternalIP(ip string) []msg.Service {
var svcs []msg.Service
for _, service := range k.APIConn.SvcExtIndexReverse(ip) {
if len(k.Namespaces) > 0 && !k.namespaceExposed(service.Namespace) {
continue
}
domain := strings.Join([]string{service.Name, service.Namespace}, ".")
svcs = append(svcs, msg.Service{Host: domain, TTL: k.ttl})
}
return svcs
}
...@@ -80,6 +80,7 @@ func (external) Run() ...@@ -80,6 +80,7 @@ func (external) Run()
func (external) Stop() error { return nil } func (external) Stop() error { return nil }
func (external) EpIndexReverse(string) []*object.Endpoints { return nil } func (external) EpIndexReverse(string) []*object.Endpoints { return nil }
func (external) SvcIndexReverse(string) []*object.Service { return nil } func (external) SvcIndexReverse(string) []*object.Service { return nil }
func (external) SvcExtIndexReverse(string) []*object.Service { return nil }
func (external) Modified(bool) int64 { return 0 } func (external) Modified(bool) int64 { return 0 }
func (external) EpIndex(s string) []*object.Endpoints { return nil } func (external) EpIndex(s string) []*object.Endpoints { return nil }
func (external) EndpointsList() []*object.Endpoints { return nil } func (external) EndpointsList() []*object.Endpoints { return nil }
......
...@@ -536,12 +536,13 @@ type APIConnServeTest struct { ...@@ -536,12 +536,13 @@ type APIConnServeTest struct {
notSynced bool notSynced bool
} }
func (a APIConnServeTest) HasSynced() bool { return !a.notSynced } func (a APIConnServeTest) HasSynced() bool { return !a.notSynced }
func (APIConnServeTest) Run() {} func (APIConnServeTest) Run() {}
func (APIConnServeTest) Stop() error { return nil } func (APIConnServeTest) Stop() error { return nil }
func (APIConnServeTest) EpIndexReverse(string) []*object.Endpoints { return nil } func (APIConnServeTest) EpIndexReverse(string) []*object.Endpoints { return nil }
func (APIConnServeTest) SvcIndexReverse(string) []*object.Service { return nil } func (APIConnServeTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnServeTest) Modified(bool) int64 { return int64(3) } func (APIConnServeTest) SvcExtIndexReverse(string) []*object.Service { return nil }
func (APIConnServeTest) Modified(bool) int64 { return int64(3) }
func (APIConnServeTest) PodIndex(ip string) []*object.Pod { func (APIConnServeTest) PodIndex(ip string) []*object.Pod {
if ip != "10.240.0.1" { if ip != "10.240.0.1" {
......
...@@ -39,13 +39,14 @@ func TestEndpointHostname(t *testing.T) { ...@@ -39,13 +39,14 @@ func TestEndpointHostname(t *testing.T) {
type APIConnServiceTest struct{} type APIConnServiceTest struct{}
func (APIConnServiceTest) HasSynced() bool { return true } func (APIConnServiceTest) HasSynced() bool { return true }
func (APIConnServiceTest) Run() {} func (APIConnServiceTest) Run() {}
func (APIConnServiceTest) Stop() error { return nil } func (APIConnServiceTest) Stop() error { return nil }
func (APIConnServiceTest) PodIndex(string) []*object.Pod { return nil } func (APIConnServiceTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnServiceTest) SvcIndexReverse(string) []*object.Service { return nil } func (APIConnServiceTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnServiceTest) EpIndexReverse(string) []*object.Endpoints { return nil } func (APIConnServiceTest) SvcExtIndexReverse(string) []*object.Service { return nil }
func (APIConnServiceTest) Modified(bool) int64 { return 0 } func (APIConnServiceTest) EpIndexReverse(string) []*object.Endpoints { return nil }
func (APIConnServiceTest) Modified(bool) int64 { return 0 }
func (APIConnServiceTest) SvcIndex(string) []*object.Service { func (APIConnServiceTest) SvcIndex(string) []*object.Service {
svcs := []*object.Service{ svcs := []*object.Service{
......
...@@ -14,14 +14,15 @@ import ( ...@@ -14,14 +14,15 @@ import (
type APIConnTest struct{} type APIConnTest struct{}
func (APIConnTest) HasSynced() bool { return true } func (APIConnTest) HasSynced() bool { return true }
func (APIConnTest) Run() {} func (APIConnTest) Run() {}
func (APIConnTest) Stop() error { return nil } func (APIConnTest) Stop() error { return nil }
func (APIConnTest) PodIndex(string) []*object.Pod { return nil } func (APIConnTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnTest) SvcIndexReverse(string) []*object.Service { return nil } func (APIConnTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnTest) EpIndex(string) []*object.Endpoints { return nil } func (APIConnTest) SvcExtIndexReverse(string) []*object.Service { return nil }
func (APIConnTest) EndpointsList() []*object.Endpoints { return nil } func (APIConnTest) EpIndex(string) []*object.Endpoints { return nil }
func (APIConnTest) Modified(bool) int64 { return 0 } func (APIConnTest) EndpointsList() []*object.Endpoints { return nil }
func (APIConnTest) Modified(bool) int64 { return 0 }
func (a APIConnTest) SvcIndex(s string) []*object.Service { func (a APIConnTest) SvcIndex(s string) []*object.Service {
switch s { switch s {
......
...@@ -15,14 +15,15 @@ import ( ...@@ -15,14 +15,15 @@ import (
type APIConnReverseTest struct{} type APIConnReverseTest struct{}
func (APIConnReverseTest) HasSynced() bool { return true } func (APIConnReverseTest) HasSynced() bool { return true }
func (APIConnReverseTest) Run() {} func (APIConnReverseTest) Run() {}
func (APIConnReverseTest) Stop() error { return nil } func (APIConnReverseTest) Stop() error { return nil }
func (APIConnReverseTest) PodIndex(string) []*object.Pod { return nil } func (APIConnReverseTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnReverseTest) EpIndex(string) []*object.Endpoints { return nil } func (APIConnReverseTest) EpIndex(string) []*object.Endpoints { return nil }
func (APIConnReverseTest) EndpointsList() []*object.Endpoints { return nil } func (APIConnReverseTest) EndpointsList() []*object.Endpoints { return nil }
func (APIConnReverseTest) ServiceList() []*object.Service { return nil } func (APIConnReverseTest) ServiceList() []*object.Service { return nil }
func (APIConnReverseTest) Modified(bool) int64 { return 0 } func (APIConnReverseTest) SvcExtIndexReverse(string) []*object.Service { return nil }
func (APIConnReverseTest) Modified(bool) int64 { return 0 }
func (APIConnReverseTest) SvcIndex(svc string) []*object.Service { func (APIConnReverseTest) SvcIndex(svc string) []*object.Service {
if svc != "svc1.testns" { if svc != "svc1.testns" {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment