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
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,
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.
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.
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).
# 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
m.Answer, m.Truncated = e.aaaa(ctx, svc, state)
case dns.TypeSRV:
m.Answer, m.Extra = e.srv(ctx, svc, state)
case dns.TypePTR:
m.Answer = e.ptr(svc, state)
default:
m.Ns = []dns.RR{e.soa(state)}
}
......
......@@ -20,7 +20,7 @@ func TestExternal(t *testing.T) {
k.APIConn = &external{}
e := New()
e.Zones = []string{"example.com."}
e.Zones = []string{"example.com.", "in-addr.arpa."}
e.Next = test.NextHandler(dns.RcodeSuccess, nil)
e.externalFunc = k.External
e.externalAddrFunc = externalAddress // internal test function
......@@ -49,12 +49,33 @@ func TestExternal(t *testing.T) {
t.Error("Expected authoritative answer")
}
if err = test.SortAndCheck(resp, tc); err != nil {
t.Error(err)
t.Errorf("Test %d: %v", i, err)
}
}
}
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
{
Qname: "svc1.testns.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess,
......@@ -155,7 +176,7 @@ var tests = []test.Case{
{
Qname: "svc11.testns.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess,
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{
test.SRV("_http._tcp.svc11.testns.example.com. 5 IN SRV 0 100 80 svc11.testns.example.com."),
},
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{
test.SRV("svc11.testns.example.com. 5 IN SRV 0 100 80 svc11.testns.example.com."),
},
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
......@@ -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) 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) {
return &object.Namespace{
Name: name,
......@@ -243,7 +278,7 @@ var svcIndexExternal = map[string][]*object.Service{
Name: "svc11",
Namespace: "testns",
Type: api.ServiceTypeLoadBalancer,
ExternalIPs: []string{"1.2.3.4"},
ExternalIPs: []string{"2.3.4.5"},
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
},
},
......
......@@ -5,6 +5,7 @@ import (
"math"
"github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
......@@ -76,6 +77,19 @@ func (e *External) aaaa(ctx context.Context, services []msg.Service, state reque
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) {
dup := make(map[item]struct{})
......
......@@ -59,6 +59,11 @@ func TestTransferAXFR(t *testing.T) {
if ans.Header().Rrtype == dns.TypeTXT {
continue
}
// Exclude PTR records
if ans.Header().Rrtype == dns.TypePTR {
continue
}
expect = append(expect, ans)
}
}
......
......@@ -25,6 +25,7 @@ const (
podIPIndex = "PodIP"
svcNameNamespaceIndex = "ServiceNameNamespace"
svcIPIndex = "ServiceIP"
svcExtIPIndex = "ServiceExternalIP"
epNameNamespaceIndex = "EndpointNameNamespace"
epIPIndex = "EndpointsIP"
)
......@@ -34,6 +35,7 @@ type dnsController interface {
EndpointsList() []*object.Endpoints
SvcIndex(string) []*object.Service
SvcIndexReverse(string) []*object.Service
SvcExtIndexReverse(string) []*object.Service
PodIndex(string) []*object.Pod
EpIndex(string) []*object.Endpoints
EpIndexReverse(string) []*object.Endpoints
......@@ -122,7 +124,7 @@ func newdnsController(ctx context.Context, kubeClient kubernetes.Interface, opts
},
&api.Service{},
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),
)
......@@ -232,12 +234,18 @@ func svcIPIndexFunc(obj interface{}) ([]string, error) {
if !ok {
return nil, errObj
}
idx := make([]string, len(svc.ClusterIPs)+len(svc.ExternalIPs))
idx := make([]string, len(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
}
......@@ -502,6 +510,22 @@ func (dns *dnsControl) SvcIndexReverse(ip string) (svcs []*object.Service) {
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) {
dns.epLock.RLock()
defer dns.epLock.RUnlock()
......
......@@ -14,6 +14,18 @@ import (
// External implements the ExternalFunc call from the external plugin.
// It returns any services matching in the services' ExternalIPs.
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)
segs := dns.SplitDomainName(base)
......@@ -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
}
......@@ -111,3 +127,24 @@ func (k *Kubernetes) ExternalServices(zone string) (services []msg.Service) {
func (k *Kubernetes) ExternalSerial(string) uint32 {
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()
func (external) Stop() error { return nil }
func (external) EpIndexReverse(string) []*object.Endpoints { 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) EpIndex(s string) []*object.Endpoints { return nil }
func (external) EndpointsList() []*object.Endpoints { return nil }
......
......@@ -536,12 +536,13 @@ type APIConnServeTest struct {
notSynced bool
}
func (a APIConnServeTest) HasSynced() bool { return !a.notSynced }
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(bool) int64 { return int64(3) }
func (a APIConnServeTest) HasSynced() bool { return !a.notSynced }
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) SvcExtIndexReverse(string) []*object.Service { return nil }
func (APIConnServeTest) Modified(bool) int64 { return int64(3) }
func (APIConnServeTest) PodIndex(ip string) []*object.Pod {
if ip != "10.240.0.1" {
......
......@@ -39,13 +39,14 @@ func TestEndpointHostname(t *testing.T) {
type APIConnServiceTest struct{}
func (APIConnServiceTest) HasSynced() bool { return true }
func (APIConnServiceTest) Run() {}
func (APIConnServiceTest) Stop() error { return nil }
func (APIConnServiceTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnServiceTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnServiceTest) EpIndexReverse(string) []*object.Endpoints { return nil }
func (APIConnServiceTest) Modified(bool) int64 { return 0 }
func (APIConnServiceTest) HasSynced() bool { return true }
func (APIConnServiceTest) Run() {}
func (APIConnServiceTest) Stop() error { return nil }
func (APIConnServiceTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnServiceTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnServiceTest) SvcExtIndexReverse(string) []*object.Service { return nil }
func (APIConnServiceTest) EpIndexReverse(string) []*object.Endpoints { return nil }
func (APIConnServiceTest) Modified(bool) int64 { return 0 }
func (APIConnServiceTest) SvcIndex(string) []*object.Service {
svcs := []*object.Service{
......
......@@ -14,14 +14,15 @@ import (
type APIConnTest struct{}
func (APIConnTest) HasSynced() bool { return true }
func (APIConnTest) Run() {}
func (APIConnTest) Stop() error { return nil }
func (APIConnTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnTest) EpIndex(string) []*object.Endpoints { return nil }
func (APIConnTest) EndpointsList() []*object.Endpoints { return nil }
func (APIConnTest) Modified(bool) int64 { return 0 }
func (APIConnTest) HasSynced() bool { return true }
func (APIConnTest) Run() {}
func (APIConnTest) Stop() error { return nil }
func (APIConnTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnTest) SvcExtIndexReverse(string) []*object.Service { return nil }
func (APIConnTest) EpIndex(string) []*object.Endpoints { return nil }
func (APIConnTest) EndpointsList() []*object.Endpoints { return nil }
func (APIConnTest) Modified(bool) int64 { return 0 }
func (a APIConnTest) SvcIndex(s string) []*object.Service {
switch s {
......
......@@ -15,14 +15,15 @@ import (
type APIConnReverseTest struct{}
func (APIConnReverseTest) HasSynced() bool { return true }
func (APIConnReverseTest) Run() {}
func (APIConnReverseTest) Stop() error { return nil }
func (APIConnReverseTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnReverseTest) EpIndex(string) []*object.Endpoints { return nil }
func (APIConnReverseTest) EndpointsList() []*object.Endpoints { return nil }
func (APIConnReverseTest) ServiceList() []*object.Service { return nil }
func (APIConnReverseTest) Modified(bool) int64 { return 0 }
func (APIConnReverseTest) HasSynced() bool { return true }
func (APIConnReverseTest) Run() {}
func (APIConnReverseTest) Stop() error { return nil }
func (APIConnReverseTest) PodIndex(string) []*object.Pod { return nil }
func (APIConnReverseTest) EpIndex(string) []*object.Endpoints { return nil }
func (APIConnReverseTest) EndpointsList() []*object.Endpoints { return nil }
func (APIConnReverseTest) ServiceList() []*object.Service { return nil }
func (APIConnReverseTest) SvcExtIndexReverse(string) []*object.Service { return nil }
func (APIConnReverseTest) Modified(bool) int64 { return 0 }
func (APIConnReverseTest) SvcIndex(svc string) []*object.Service {
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